aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/build.gradle.kts10
-rw-r--r--subprojects/frontend/config/detectDevModeOptions.ts4
-rw-r--r--subprojects/frontend/config/graphvizUMDVitePlugin.ts69
-rw-r--r--subprojects/frontend/index.html3
-rw-r--r--subprojects/frontend/package.json87
-rw-r--r--subprojects/frontend/src/DirectionalSplitPane.tsx159
-rw-r--r--subprojects/frontend/src/PaneButtons.tsx144
-rw-r--r--subprojects/frontend/src/Refinery.tsx4
-rw-r--r--subprojects/frontend/src/TopBar.tsx85
-rw-r--r--subprojects/frontend/src/WorkArea.tsx33
-rw-r--r--subprojects/frontend/src/editor/AnalysisErrorNotification.tsx74
-rw-r--r--subprojects/frontend/src/editor/AnimatedButton.tsx9
-rw-r--r--subprojects/frontend/src/editor/DiagnosticValue.ts1
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx6
-rw-r--r--subprojects/frontend/src/editor/EditorErrors.tsx93
-rw-r--r--subprojects/frontend/src/editor/EditorPane.tsx4
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts47
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts15
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx48
-rw-r--r--subprojects/frontend/src/graph/DotGraphVisualizer.tsx162
-rw-r--r--subprojects/frontend/src/graph/GraphArea.tsx54
-rw-r--r--subprojects/frontend/src/graph/GraphPane.tsx28
-rw-r--r--subprojects/frontend/src/graph/GraphStore.ts187
-rw-r--r--subprojects/frontend/src/graph/GraphTheme.tsx120
-rw-r--r--subprojects/frontend/src/graph/RelationName.tsx72
-rw-r--r--subprojects/frontend/src/graph/VisibilityDialog.tsx315
-rw-r--r--subprojects/frontend/src/graph/VisibilityPanel.tsx91
-rw-r--r--subprojects/frontend/src/graph/ZoomButtons.tsx49
-rw-r--r--subprojects/frontend/src/graph/ZoomCanvas.tsx224
-rw-r--r--subprojects/frontend/src/graph/dotSource.ts340
-rw-r--r--subprojects/frontend/src/graph/parseBBox.ts68
-rw-r--r--subprojects/frontend/src/graph/postProcessSVG.ts186
-rw-r--r--subprojects/frontend/src/index.tsx29
-rw-r--r--subprojects/frontend/src/language/indentation.ts2
-rw-r--r--subprojects/frontend/src/table/RelationGrid.tsx109
-rw-r--r--subprojects/frontend/src/table/SymbolSelector.tsx65
-rw-r--r--subprojects/frontend/src/table/TableArea.tsx24
-rw-r--r--subprojects/frontend/src/table/TablePane.tsx22
-rw-r--r--subprojects/frontend/src/table/TableToolbar.tsx41
-rw-r--r--subprojects/frontend/src/table/ValueRenderer.tsx62
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx14
-rw-r--r--subprojects/frontend/src/theme/ThemeStore.ts48
-rw-r--r--subprojects/frontend/src/utils/svgURL.ts9
-rw-r--r--subprojects/frontend/src/xtext/BackendConfig.ts2
-rw-r--r--subprojects/frontend/src/xtext/ContentAssistService.ts18
-rw-r--r--subprojects/frontend/src/xtext/SemanticsService.ts32
-rw-r--r--subprojects/frontend/src/xtext/UpdateService.ts2
-rw-r--r--subprojects/frontend/src/xtext/ValidationService.ts44
-rw-r--r--subprojects/frontend/src/xtext/XtextClient.ts13
-rw-r--r--subprojects/frontend/src/xtext/XtextWebSocketClient.ts5
-rw-r--r--subprojects/frontend/src/xtext/xtextMessages.ts6
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts46
-rw-r--r--subprojects/frontend/tsconfig.base.json2
-rw-r--r--subprojects/frontend/types/ImportMeta.d.ts2
-rw-r--r--subprojects/frontend/types/grammar.d.ts2
-rw-r--r--subprojects/frontend/types/node/@lezer-generator-rollup.d.ts2
-rw-r--r--subprojects/frontend/vite.config.ts6
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java52
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java13
-rw-r--r--subprojects/language-model/problem.aird153
-rw-r--r--subprojects/language-model/src/main/resources/model/problem.ecore11
-rw-r--r--subprojects/language-model/src/main/resources/model/problem.genmodel10
-rw-r--r--subprojects/language-semantics/build.gradle.kts8
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/BuiltInDetail.java10
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ClassDetail.java16
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/Metadata.java12
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java181
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeKind.java12
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeMetadata.java9
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/OppositeReferenceDetail.java9
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/PredicateDetail.java16
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ReferenceDetail.java16
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationDetail.java10
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationMetadata.java9
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java665
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java31
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java51
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java32
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java9
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/MutableSeed.java28
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/NullaryMutableSeed.java83
-rw-r--r--subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/internal/DecisionTreeTests.java (renamed from subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java)14
-rw-r--r--subprojects/language-web/build.gradle.kts19
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java6
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java4
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java4
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java33
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java3
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java99
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java9
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java12
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java142
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java16
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java175
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java110
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java48
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java6
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java9
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java12
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java50
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java8
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java9
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java304
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java26
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java19
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java1
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java2
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java10
-rw-r--r--subprojects/language/build.gradle.kts8
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/Problem.xtext48
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java96
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java1
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java44
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java6
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java15
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java22
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java9
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java24
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java47
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java29
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java255
-rw-r--r--subprojects/language/src/main/resources/tools/refinery/language/builtin.problem41
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java3
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java4
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java4
-rw-r--r--subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java243
-rw-r--r--subprojects/store-dse-visualization/build.gradle.kts (renamed from subprojects/visualization/build.gradle.kts)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java)0
-rw-r--r--subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java (renamed from subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java)0
-rw-r--r--subprojects/store-dse/build.gradle.kts7
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/DanglingEdges.java12
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java2
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/CreateActionLiteral.java43
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/DeleteActionLiteral.java51
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/ModificationActionLiterals.java23
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java55
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java5
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java11
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java8
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java2
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Rule.java99
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/RuleBuilder.java71
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java24
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java63
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/AbstractActionLiteral.java9
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/Action.java132
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiteral.java19
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiterals.java33
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundAction.java109
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundActionLiteral.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/PutActionLiteral.java64
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback0.java15
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback1.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback2.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback3.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback4.java16
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback0.java13
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback1.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback2.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback3.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback4.java14
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java30
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java32
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/AndCriterion.java53
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeCriterion.java43
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeObjective.java69
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CountObjective.java47
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criteria.java47
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java9
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java10
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objectives.java41
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/OrCriterion.java53
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriterion.java (renamed from subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java)29
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java17
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java6
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java6
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java6
-rw-r--r--subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java6
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java184
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java93
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java596
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java414
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java2
-rw-r--r--subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/TransitionTests.java40
-rw-r--r--subprojects/store-query-viatra/build.gradle.kts10
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java17
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java8
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java89
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java94
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java30
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java10
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java8
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java33
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java12
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java60
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java28
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java70
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java4
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java4
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java53
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/CheckEvaluator.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java)6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java151
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java24
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java4
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java8
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java8
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java3
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java42
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java93
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/StronglyConnectedComponentsTest.java261
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/WeaklyConnectedComponentsTest.java188
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java2
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java7
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java102
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java73
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java112
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java59
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java42
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java60
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java107
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java49
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java28
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CheckLiteral.java (renamed from subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java)50
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java38
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java61
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java119
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java160
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java98
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java51
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java24
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java12
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java4
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java25
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java16
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java55
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java4
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/HashCodeTest.java67
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java7
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java189
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java13
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/DuplicateDnfRemoverTest.java164
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/InputParameterResolverTest.java228
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java6
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java2
-rw-r--r--subprojects/store-reasoning-scope/build.gradle.kts17
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorAdapter.java21
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorBuilder.java21
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorStoreAdapter.java16
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/LowerTypeScopePropagator.java86
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/RoundingUtil.java26
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java253
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java86
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorStoreAdapterImpl.java58
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/TypeScopePropagator.java67
-rw-r--r--subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/UpperTypeScopePropagator.java59
-rw-r--r--subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java83
-rw-r--r--subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MultiObjectTest.java206
-rw-r--r--subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/internal/RoundingUtilTest.java69
-rw-r--r--subprojects/store-reasoning/build.gradle.kts4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java25
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java41
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java41
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java10
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java48
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java60
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java223
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java53
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java180
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java150
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java86
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AnyPartialInterpretation.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java)8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java34
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java195
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java63
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java182
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java136
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java18
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java51
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java19
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java29
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java79
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java14
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java34
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementResult.java24
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java111
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java95
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java59
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java28
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java27
-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.java30
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java390
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java212
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java12
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java27
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java35
-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.java93
-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.java54
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java255
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java24
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java128
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java35
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java57
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java46
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java94
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java44
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java83
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java33
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java225
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java13
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java64
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java85
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java55
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java131
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java45
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java86
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java139
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java14
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java28
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java77
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java62
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java93
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java52
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java)21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java141
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java138
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java)109
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java66
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java61
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java110
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java44
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java108
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java395
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java128
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java58
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java152
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/CandidateCountTest.java321
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java321
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java145
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisExampleHierarchyTest.java (renamed from subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java)92
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java205
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java186
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java224
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTester.java (renamed from subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java)27
-rw-r--r--subprojects/store/build.gradle.kts2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java64
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/IteratorBasedCursor.java44
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreConfiguration.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/BaseIndexer.java102
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java48
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java13
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java27
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java48
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java33
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityDomain.java68
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java7
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java12
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java7
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java42
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java18
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java13
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java82
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java57
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java53
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java2
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java5
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java8
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java4
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java7
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java11
-rw-r--r--subprojects/viatra-runtime-localsearch/about.html26
-rw-r--r--subprojects/viatra-runtime-localsearch/build.gradle.kts14
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java83
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java114
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java33
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java55
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java29
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java120
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java120
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java301
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java97
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java532
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java37
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java29
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java44
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java49
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java72
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java41
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java259
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java65
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java24
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java70
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java306
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java50
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java)23
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java27
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java26
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java88
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java41
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java116
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java135
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java70
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java96
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java92
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java78
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java95
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java77
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java89
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java106
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java88
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java104
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java185
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java75
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java)57
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java40
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java96
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java)54
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java114
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java186
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java49
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java33
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java84
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java99
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java210
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java115
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java49
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java38
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java23
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java140
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java257
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java41
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java142
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java278
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java283
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java430
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java101
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java53
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java63
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java22
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java91
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java49
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java413
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java95
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java209
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java26
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java83
-rw-r--r--subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF18
-rw-r--r--subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license4
-rw-r--r--subprojects/viatra-runtime-rete-recipes/about.html26
-rw-r--r--subprojects/viatra-runtime-rete-recipes/build.gradle.kts62
-rw-r--r--subprojects/viatra-runtime-rete-recipes/build.properties15
-rw-r--r--subprojects/viatra-runtime-rete-recipes/plugin.properties9
-rw-r--r--subprojects/viatra-runtime-rete-recipes/plugin.xml23
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe225
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java204
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java81
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore400
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license4
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel171
-rw-r--r--subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license4
-rw-r--r--subprojects/viatra-runtime-rete/about.html26
-rw-r--r--subprojects/viatra-runtime-rete/build.gradle.kts15
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java474
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java369
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java38
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java120
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java114
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java26
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java278
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java212
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java279
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java247
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java106
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java117
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java212
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java26
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java209
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java68
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java208
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java551
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java50
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java171
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java90
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java390
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java86
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java949
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java162
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java49
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java205
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java30
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java65
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java180
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java25
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java75
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java311
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java183
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java28
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java348
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java199
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java76
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java76
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java71
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java41
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java284
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java34
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java193
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java55
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java54
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java88
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java47
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java21
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java176
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java127
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java121
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java51
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java49
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingAlg.java226
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingTcRelation.java259
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/CountingListener.java36
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/IncSCCAlg.java609
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/DFSPathFinder.java146
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Edge.java38
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/GraphHelper.java169
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/IGraphPathFinder.java67
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/ITcRelation.java31
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Tuple.java60
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/bfs/BFS.java148
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/PKAlg.java179
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCC.java143
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCProperty.java37
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCResult.java81
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/topsort/TopologicalSorting.java73
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java174
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeObserver.java12
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java69
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java85
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/util/CollectionHelper.java64
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/DotGenerator.java160
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/Graph.java185
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalGraphDataSource.java37
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalWrapper.java110
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphDataSource.java70
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphObserver.java55
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcDataSource.java82
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcObserver.java39
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java49
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java46
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java30
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java100
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java35
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java579
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java463
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java61
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java64
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java43
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java50
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java43
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java111
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java109
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java108
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java171
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java31
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java408
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java30
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java62
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java376
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java346
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java39
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java28
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java85
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java34
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java14
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java729
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java123
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java82
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java19
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java31
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java103
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java467
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java19
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java32
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java34
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java124
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java164
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java86
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java149
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java36
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java171
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java216
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java81
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java102
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java48
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java81
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java27
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java30
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java74
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java95
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java33
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java32
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java30
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java78
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java23
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java109
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java117
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java163
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java218
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java135
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java150
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java125
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java63
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java54
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java138
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java37
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java79
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java85
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java154
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java41
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java69
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java52
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java125
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java126
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java63
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java161
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java49
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java147
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java48
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java61
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java321
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java44
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java24
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java54
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java51
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java42
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java17
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java80
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java81
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java33
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java36
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java58
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java111
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java92
-rw-r--r--subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java60
-rw-r--r--subprojects/viatra-runtime/about.html26
-rw-r--r--subprojects/viatra-runtime/build.gradle.kts15
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java13
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java363
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java166
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java83
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java82
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java76
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java37
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java107
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java46
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java86
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java105
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java150
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java26
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java52
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java191
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java293
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java258
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java55
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java31
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java350
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java91
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java147
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java91
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java49
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java23
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java21
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java34
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java71
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java24
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java32
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java693
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java138
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java43
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java214
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java42
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java24
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java82
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java62
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java135
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java82
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java61
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java82
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java61
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java39
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java44
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java44
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java39
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java83
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java214
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java32
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java36
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java89
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java26
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java68
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java52
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java46
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java32
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java202
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java241
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java122
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java74
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java69
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java21
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java45
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java35
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java49
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java39
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java98
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java31
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java287
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java36
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java103
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java55
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java165
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java153
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java58
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java127
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java77
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java385
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java85
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java143
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java228
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java100
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java98
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java106
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java108
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java133
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java108
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java29
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java102
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java240
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java165
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java143
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java62
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java217
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java94
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java76
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java64
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java52
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java109
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java90
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java108
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java31
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java59
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java42
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java26
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java47
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java65
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java28
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java56
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java39
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java289
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java70
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java16
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java203
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java153
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java40
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java31
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java49
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java61
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java40
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java106
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java194
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java94
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java30
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java98
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java99
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java96
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java108
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java80
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java151
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java52
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java118
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java70
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java57
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java105
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java44
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java57
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java11
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java57
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java76
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java43
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java79
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java231
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java104
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java105
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java35
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java68
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java110
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java154
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java101
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java37
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java35
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java53
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java23
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java23
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java129
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java48
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java19
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java50
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java55
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java33
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java59
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java135
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java26
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java68
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java307
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java310
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java64
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java253
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java31
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java63
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java88
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java136
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java20
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java65
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java60
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java46
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java44
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java51
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java55
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java59
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java64
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java172
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple1.java83
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple2.java73
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple3.java81
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple4.java88
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/MaskedTuple.java48
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuple.java69
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask.java560
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java56
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java51
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java48
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java157
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java50
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java47
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java47
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java48
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java23
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java188
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java61
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java86
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java41
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java159
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java150
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java212
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java226
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java93
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java94
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java93
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java32
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java26
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java81
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java205
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java216
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java485
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java30
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java30
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java37
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java102
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java21
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java117
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java208
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java44
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java90
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java60
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java29
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java105
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java517
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java36
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java27
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java36
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java111
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java55
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java73
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java146
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java46
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java72
1030 files changed, 75915 insertions, 5270 deletions
diff --git a/subprojects/frontend/build.gradle.kts b/subprojects/frontend/build.gradle.kts
index d0839371..286dd05c 100644
--- a/subprojects/frontend/build.gradle.kts
+++ b/subprojects/frontend/build.gradle.kts
@@ -16,9 +16,9 @@ frontend {
16 assembleScript.set("run build") 16 assembleScript.set("run build")
17} 17}
18 18
19val viteOutputDir = "$buildDir/vite" 19val viteOutputDir = layout.buildDirectory.dir("vite")
20 20
21val productionResources = file("$viteOutputDir/production") 21val productionResources = viteOutputDir.map { it.dir("production") }
22 22
23val productionAssets: Configuration by configurations.creating { 23val productionAssets: Configuration by configurations.creating {
24 isCanBeConsumed = true 24 isCanBeConsumed = true
@@ -81,7 +81,7 @@ tasks {
81 dependsOn(installFrontend) 81 dependsOn(installFrontend)
82 dependsOn(generateXStateTypes) 82 dependsOn(generateXStateTypes)
83 inputs.files(lintingFiles) 83 inputs.files(lintingFiles)
84 outputs.dir("$buildDir/typescript") 84 outputs.dir(layout.buildDirectory.dir("typescript"))
85 script.set("run typecheck") 85 script.set("run typecheck")
86 group = "verification" 86 group = "verification"
87 description = "Check for TypeScript type errors." 87 description = "Check for TypeScript type errors."
@@ -92,7 +92,7 @@ tasks {
92 dependsOn(generateXStateTypes) 92 dependsOn(generateXStateTypes)
93 dependsOn(typeCheckFrontend) 93 dependsOn(typeCheckFrontend)
94 inputs.files(lintingFiles) 94 inputs.files(lintingFiles)
95 outputs.file("$buildDir/eslint.json") 95 outputs.file(layout.buildDirectory.file("eslint.json"))
96 script.set("run lint") 96 script.set("run lint")
97 group = "verification" 97 group = "verification"
98 description = "Check for TypeScript lint errors and warnings." 98 description = "Check for TypeScript lint errors and warnings."
@@ -140,5 +140,5 @@ artifacts {
140sonarqube.properties { 140sonarqube.properties {
141 SonarPropertiesUtils.addToList(properties, "sonar.sources", "src") 141 SonarPropertiesUtils.addToList(properties, "sonar.sources", "src")
142 property("sonar.nodejs.executable", "${frontend.nodeInstallDirectory.get()}/bin/node") 142 property("sonar.nodejs.executable", "${frontend.nodeInstallDirectory.get()}/bin/node")
143 property("sonar.eslint.reportPaths", "$buildDir/eslint.json") 143 property("sonar.eslint.reportPaths", "${layout.buildDirectory.get()}/eslint.json")
144} 144}
diff --git a/subprojects/frontend/config/detectDevModeOptions.ts b/subprojects/frontend/config/detectDevModeOptions.ts
index 665204dc..6052e047 100644
--- a/subprojects/frontend/config/detectDevModeOptions.ts
+++ b/subprojects/frontend/config/detectDevModeOptions.ts
@@ -30,8 +30,8 @@ function detectListenOptions(
30 fallbackHost: string, 30 fallbackHost: string,
31 fallbackPort: number, 31 fallbackPort: number,
32): ListenOptions { 32): ListenOptions {
33 const host = process.env[`${name}_HOST`] ?? fallbackHost; 33 const host = process.env[`REFINERY_${name}_HOST`] ?? fallbackHost;
34 const rawPort = process.env[`${name}_PORT`]; 34 const rawPort = process.env[`REFINERY_${name}_PORT`];
35 const port = rawPort === undefined ? fallbackPort : parseInt(rawPort, 10); 35 const port = rawPort === undefined ? fallbackPort : parseInt(rawPort, 10);
36 const secure = port === 443; 36 const secure = port === 443;
37 return { host, port, secure }; 37 return { host, port, secure };
diff --git a/subprojects/frontend/config/graphvizUMDVitePlugin.ts b/subprojects/frontend/config/graphvizUMDVitePlugin.ts
new file mode 100644
index 00000000..9c60a84e
--- /dev/null
+++ b/subprojects/frontend/config/graphvizUMDVitePlugin.ts
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { readFile } from 'node:fs/promises';
8import path from 'node:path';
9
10import pnpapi from 'pnpapi';
11import type { PluginOption, ResolvedConfig } from 'vite';
12
13// Use a CJS file as the PnP resolution issuer to force resolution to a non-ESM export.
14const issuerFileName = 'worker.cjs';
15
16export default function graphvizUMDVitePlugin(): PluginOption {
17 let command: ResolvedConfig['command'] = 'build';
18 let root: string | undefined;
19 let url: string | undefined;
20
21 return {
22 name: 'graphviz-umd',
23 enforce: 'post',
24 configResolved(config) {
25 ({ command, root } = config);
26 },
27 async buildStart() {
28 const issuer =
29 root === undefined ? issuerFileName : path.join(issuerFileName);
30 const resolvedPath = pnpapi.resolveRequest(
31 '@hpcc-js/wasm/graphviz',
32 issuer,
33 );
34 if (resolvedPath === null) {
35 return;
36 }
37 if (command === 'serve') {
38 url = `/@fs/${resolvedPath}`;
39 } else {
40 const content = await readFile(resolvedPath, null);
41 url = this.emitFile({
42 name: path.basename(resolvedPath),
43 type: 'asset',
44 source: content,
45 });
46 }
47 },
48 renderStart() {
49 if (url !== undefined && command !== 'serve') {
50 url = this.getFileName(url);
51 }
52 },
53 transformIndexHtml() {
54 if (url === undefined) {
55 return undefined;
56 }
57 return [
58 {
59 tag: 'script',
60 attrs: {
61 src: url,
62 type: 'javascript/worker',
63 },
64 injectTo: 'head',
65 },
66 ];
67 },
68 };
69}
diff --git a/subprojects/frontend/index.html b/subprojects/frontend/index.html
index 1bf3472e..8992d538 100644
--- a/subprojects/frontend/index.html
+++ b/subprojects/frontend/index.html
@@ -18,7 +18,8 @@
18 <meta name="theme-color" media="(prefers-color-scheme:light)" content="#f5f5f5"> 18 <meta name="theme-color" media="(prefers-color-scheme:light)" content="#f5f5f5">
19 <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b"> 19 <meta name="theme-color" media="(prefers-color-scheme:dark)" content="#21252b">
20 <style> 20 <style>
21 @import '@fontsource-variable/inter/wght.css'; 21 @import '@fontsource-variable/open-sans/wdth.css';
22 @import '@fontsource-variable/open-sans/wdth-italic.css';
22 @import '@fontsource-variable/jetbrains-mono/wght.css'; 23 @import '@fontsource-variable/jetbrains-mono/wght.css';
23 @import '@fontsource-variable/jetbrains-mono/wght-italic.css'; 24 @import '@fontsource-variable/jetbrains-mono/wght-italic.css';
24 </style> 25 </style>
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index ba8a0a58..179e09d9 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -28,71 +28,84 @@
28 }, 28 },
29 "homepage": "https://refinery.tools", 29 "homepage": "https://refinery.tools",
30 "dependencies": { 30 "dependencies": {
31 "@codemirror/autocomplete": "^6.8.0", 31 "@codemirror/autocomplete": "^6.9.0",
32 "@codemirror/commands": "^6.2.4", 32 "@codemirror/commands": "^6.2.5",
33 "@codemirror/language": "^6.8.0", 33 "@codemirror/language": "^6.9.0",
34 "@codemirror/lint": "^6.2.2", 34 "@codemirror/lint": "^6.4.1",
35 "@codemirror/search": "^6.5.0", 35 "@codemirror/search": "^6.5.2",
36 "@codemirror/state": "^6.2.1", 36 "@codemirror/state": "^6.2.1",
37 "@codemirror/view": "^6.13.2", 37 "@codemirror/view": "^6.17.0",
38 "@emotion/react": "^11.11.1", 38 "@emotion/react": "^11.11.1",
39 "@emotion/styled": "^11.11.0", 39 "@emotion/styled": "^11.11.0",
40 "@fontsource-variable/inter": "^5.0.3", 40 "@fontsource-variable/jetbrains-mono": "^5.0.9",
41 "@fontsource-variable/jetbrains-mono": "^5.0.3", 41 "@fontsource-variable/open-sans": "^5.0.9",
42 "@lezer/common": "^1.0.3", 42 "@hpcc-js/wasm": "^2.13.1",
43 "@lezer/common": "^1.0.4",
43 "@lezer/highlight": "^1.1.6", 44 "@lezer/highlight": "^1.1.6",
44 "@lezer/lr": "^1.3.6", 45 "@lezer/lr": "^1.3.10",
45 "@material-icons/svg": "^1.0.33", 46 "@material-icons/svg": "^1.0.33",
46 "@mui/icons-material": "5.11.16", 47 "@mui/icons-material": "5.14.7",
47 "@mui/material": "5.13.5", 48 "@mui/material": "5.14.7",
48 "@vitejs/plugin-react-swc": "^3.3.2", 49 "@mui/system": "^5.14.7",
50 "@mui/x-data-grid": "^6.10.0 <6.10.1",
49 "ansi-styles": "^6.2.1", 51 "ansi-styles": "^6.2.1",
50 "csstype": "^3.1.2", 52 "csstype": "^3.1.2",
53 "d3": "^7.8.5",
54 "d3-graphviz": "patch:d3-graphviz@npm%3A5.1.0#~/.yarn/patches/d3-graphviz-npm-5.1.0-ba6bed3fec.patch",
55 "d3-selection": "^3.0.0",
56 "d3-zoom": "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch",
51 "escape-string-regexp": "^5.0.0", 57 "escape-string-regexp": "^5.0.0",
52 "lodash-es": "^4.17.21", 58 "lodash-es": "^4.17.21",
53 "loglevel": "^1.8.1", 59 "loglevel": "^1.8.1",
54 "loglevel-plugin-prefix": "^0.8.4", 60 "loglevel-plugin-prefix": "^0.8.4",
55 "mobx": "^6.9.0", 61 "mobx": "^6.10.2",
56 "mobx-react-lite": "^3.4.3", 62 "mobx-react-lite": "^4.0.4",
57 "ms": "^2.1.3", 63 "ms": "^2.1.3",
58 "nanoid": "^4.0.2", 64 "nanoid": "^4.0.2",
59 "notistack": "^3.0.1", 65 "notistack": "^3.0.1",
60 "react": "^18.2.0", 66 "react": "^18.2.0",
61 "react-dom": "^18.2.0", 67 "react-dom": "^18.2.0",
62 "xstate": "^4.37.2", 68 "react-resize-detector": "^9.1.0",
63 "zod": "^3.21.4" 69 "xstate": "^4.38.2",
70 "zod": "^3.22.2"
64 }, 71 },
65 "devDependencies": { 72 "devDependencies": {
66 "@lezer/generator": "^1.3.0", 73 "@lezer/generator": "^1.5.0",
67 "@types/eslint": "^8.40.2", 74 "@types/d3": "^7.4.0",
75 "@types/d3-graphviz": "^2.6.7",
76 "@types/d3-selection": "^3.0.6",
77 "@types/d3-zoom": "^3.0.4",
78 "@types/eslint": "^8.44.2",
68 "@types/html-minifier-terser": "^7.0.0", 79 "@types/html-minifier-terser": "^7.0.0",
69 "@types/lodash-es": "^4.17.7", 80 "@types/lodash-es": "^4.17.9",
70 "@types/micromatch": "^4.0.2", 81 "@types/micromatch": "^4.0.2",
71 "@types/ms": "^0.7.31", 82 "@types/ms": "^0.7.31",
72 "@types/node": "^18.16.18", 83 "@types/node": "^18.17.12",
73 "@types/prettier": "^2.7.3", 84 "@types/pnpapi": "^0.0.2",
74 "@types/react": "^18.2.12", 85 "@types/react": "^18.2.21",
75 "@types/react-dom": "^18.2.5", 86 "@types/react-dom": "^18.2.7",
76 "@typescript-eslint/eslint-plugin": "^5.59.11", 87 "@typescript-eslint/eslint-plugin": "^6.5.0",
77 "@typescript-eslint/parser": "^5.59.11", 88 "@typescript-eslint/parser": "^6.5.0",
78 "@xstate/cli": "^0.5.1", 89 "@vitejs/plugin-react-swc": "^3.3.2",
90 "@xstate/cli": "^0.5.2",
79 "cross-env": "^7.0.3", 91 "cross-env": "^7.0.3",
80 "eslint": "^8.43.0", 92 "eslint": "^8.48.0",
81 "eslint-config-airbnb": "^19.0.4", 93 "eslint-config-airbnb": "^19.0.4",
82 "eslint-config-airbnb-typescript": "^17.0.0", 94 "eslint-config-airbnb-typescript": "^17.1.0",
83 "eslint-config-prettier": "^8.8.0", 95 "eslint-config-prettier": "^9.0.0",
84 "eslint-import-resolver-typescript": "^3.5.5", 96 "eslint-import-resolver-typescript": "^3.6.0",
85 "eslint-plugin-import": "^2.27.5", 97 "eslint-plugin-import": "^2.28.1",
86 "eslint-plugin-jsx-a11y": "^6.7.1", 98 "eslint-plugin-jsx-a11y": "^6.7.1",
87 "eslint-plugin-mobx": "^0.0.9", 99 "eslint-plugin-mobx": "^0.0.9",
88 "eslint-plugin-prettier": "^4.2.1", 100 "eslint-plugin-prettier": "^5.0.0",
89 "eslint-plugin-react": "^7.32.2", 101 "eslint-plugin-react": "^7.33.2",
90 "eslint-plugin-react-hooks": "^4.6.0", 102 "eslint-plugin-react-hooks": "^4.6.0",
91 "html-minifier-terser": "^7.2.0", 103 "html-minifier-terser": "^7.2.0",
92 "micromatch": "^4.0.5", 104 "micromatch": "^4.0.5",
93 "prettier": "^2.8.8", 105 "pnpapi": "^0.0.0",
94 "typescript": "5.1.3", 106 "prettier": "^3.0.3",
95 "vite": "^4.3.9", 107 "typescript": "5.2.2",
108 "vite": "^4.4.9",
96 "vite-plugin-pwa": "^0.16.4", 109 "vite-plugin-pwa": "^0.16.4",
97 "workbox-window": "^7.0.0" 110 "workbox-window": "^7.0.0"
98 } 111 }
diff --git a/subprojects/frontend/src/DirectionalSplitPane.tsx b/subprojects/frontend/src/DirectionalSplitPane.tsx
new file mode 100644
index 00000000..59c8b739
--- /dev/null
+++ b/subprojects/frontend/src/DirectionalSplitPane.tsx
@@ -0,0 +1,159 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
8import MoreVertIcon from '@mui/icons-material/MoreVert';
9import Box from '@mui/material/Box';
10import Stack from '@mui/material/Stack';
11import { alpha, useTheme } from '@mui/material/styles';
12import { useCallback, useRef, useState } from 'react';
13import { useResizeDetector } from 'react-resize-detector';
14
15export default function DirectionalSplitPane({
16 primary: left,
17 secondary: right,
18 primaryOnly: showLeftOnly,
19 secondaryOnly: showRightOnly,
20}: {
21 primary: React.ReactNode;
22 secondary: React.ReactNode;
23 primaryOnly?: boolean;
24 secondaryOnly?: boolean;
25}): JSX.Element {
26 const theme = useTheme();
27 const stackRef = useRef<HTMLDivElement | null>(null);
28 const { ref: resizeRef, width, height } = useResizeDetector();
29 const sliderRef = useRef<HTMLDivElement>(null);
30 const [resizing, setResizing] = useState(false);
31 const [fraction, setFraction] = useState(0.5);
32
33 const horizontalSplit =
34 width !== undefined && height !== undefined && height > width;
35 const direction = horizontalSplit ? 'column' : 'row';
36 const axis = horizontalSplit ? 'height' : 'width';
37 const primarySize = showLeftOnly
38 ? '100%'
39 : `calc(${fraction * 100}% - 0.5px)`;
40 const secondarySize = showRightOnly
41 ? '100%'
42 : `calc(${(1 - fraction) * 100}% - 0.5px)`;
43 const ref = useCallback(
44 (element: HTMLDivElement | null) => {
45 resizeRef(element);
46 stackRef.current = element;
47 },
48 [resizeRef],
49 );
50
51 return (
52 <Stack
53 direction={direction}
54 height="100%"
55 width="100%"
56 overflow="hidden"
57 ref={ref}
58 >
59 {!showRightOnly && <Box {...{ [axis]: primarySize }}>{left}</Box>}
60 <Box
61 sx={{
62 overflow: 'visible',
63 position: 'relative',
64 [axis]: '0px',
65 display: showLeftOnly || showRightOnly ? 'none' : 'flex',
66 flexDirection: direction,
67 [horizontalSplit
68 ? 'borderBottom'
69 : 'borderRight']: `1px solid ${theme.palette.outer.border}`,
70 }}
71 >
72 <Box
73 ref={sliderRef}
74 sx={{
75 display: 'flex',
76 position: 'absolute',
77 [axis]: theme.spacing(2),
78 ...(horizontalSplit
79 ? {
80 top: theme.spacing(-1),
81 left: 0,
82 right: 0,
83 transform: 'translateY(0.5px)',
84 }
85 : {
86 left: theme.spacing(-1),
87 top: 0,
88 bottom: 0,
89 transform: 'translateX(0.5px)',
90 }),
91 zIndex: 999,
92 alignItems: 'center',
93 justifyContent: 'center',
94 color: theme.palette.text.secondary,
95 cursor: horizontalSplit ? 'ns-resize' : 'ew-resize',
96 '.MuiSvgIcon-root': {
97 opacity: resizing ? 1 : 0,
98 },
99 ...(resizing
100 ? {
101 background: alpha(
102 theme.palette.text.primary,
103 theme.palette.action.activatedOpacity,
104 ),
105 }
106 : {
107 '&:hover': {
108 background: alpha(
109 theme.palette.text.primary,
110 theme.palette.action.hoverOpacity,
111 ),
112 '.MuiSvgIcon-root': {
113 opacity: 1,
114 },
115 },
116 }),
117 }}
118 onPointerDown={(event) => {
119 if (event.button !== 0) {
120 return;
121 }
122 sliderRef.current?.setPointerCapture(event.pointerId);
123 setResizing(true);
124 }}
125 onPointerUp={(event) => {
126 if (event.button !== 0) {
127 return;
128 }
129 sliderRef.current?.releasePointerCapture(event.pointerId);
130 setResizing(false);
131 }}
132 onPointerMove={(event) => {
133 if (!resizing) {
134 return;
135 }
136 const container = stackRef.current;
137 if (container === null) {
138 return;
139 }
140 const rect = container.getBoundingClientRect();
141 const newFraction = horizontalSplit
142 ? (event.clientY - rect.top) / rect.height
143 : (event.clientX - rect.left) / rect.width;
144 setFraction(Math.min(0.9, Math.max(0.1, newFraction)));
145 }}
146 onDoubleClick={() => setFraction(0.5)}
147 >
148 {horizontalSplit ? <MoreHorizIcon /> : <MoreVertIcon />}
149 </Box>
150 </Box>
151 {!showLeftOnly && <Box {...{ [axis]: secondarySize }}>{right}</Box>}
152 </Stack>
153 );
154}
155
156DirectionalSplitPane.defaultProps = {
157 primaryOnly: false,
158 secondaryOnly: false,
159};
diff --git a/subprojects/frontend/src/PaneButtons.tsx b/subprojects/frontend/src/PaneButtons.tsx
new file mode 100644
index 00000000..7e884ab0
--- /dev/null
+++ b/subprojects/frontend/src/PaneButtons.tsx
@@ -0,0 +1,144 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import CodeIcon from '@mui/icons-material/Code';
8import SchemaRoundedIcon from '@mui/icons-material/SchemaRounded';
9import TableChartIcon from '@mui/icons-material/TableChart';
10import ToggleButton from '@mui/material/ToggleButton';
11import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
12import { alpha, styled } from '@mui/material/styles';
13import { observer } from 'mobx-react-lite';
14
15import type ThemeStore from './theme/ThemeStore';
16
17const PaneButtonGroup = styled(ToggleButtonGroup, {
18 name: 'PaneButtons-Group',
19 shouldForwardProp: (prop) => prop !== 'hideLabel',
20})<{ hideLabel: boolean }>(({ theme, hideLabel }) => {
21 const color =
22 theme.palette.mode === 'dark'
23 ? theme.palette.primary.main
24 : theme.palette.text.primary;
25 return {
26 gap: theme.spacing(1),
27 '.MuiToggleButton-root': {
28 fontSize: '1rem',
29 lineHeight: '1.5',
30 border: 'none',
31 ...(hideLabel ? {} : { paddingBlock: 6 }),
32 '&::before': {
33 content: '" "',
34 position: 'absolute',
35 bottom: 0,
36 left: 0,
37 width: '0%',
38 height: '2px',
39 background: color,
40 transition: theme.transitions.create('width', {
41 duration: theme.transitions.duration.standard,
42 }),
43 },
44 '&.MuiToggleButtonGroup-grouped': {
45 borderTopLeftRadius: theme.shape.borderRadius,
46 borderTopRightRadius: theme.shape.borderRadius,
47 borderBottomLeftRadius: 0,
48 borderBottomRightRadius: 0,
49 },
50 '&:not(.Mui-selected)': {
51 color: theme.palette.text.secondary,
52 },
53 '&.Mui-selected': {
54 color,
55 '&::before': {
56 width: '100%',
57 },
58 '&:not(:active)': {
59 background: 'transparent',
60 },
61 '&:hover': {
62 background: alpha(
63 theme.palette.text.primary,
64 theme.palette.action.hoverOpacity,
65 ),
66 '@media (hover: none)': {
67 background: 'transparent',
68 },
69 },
70 },
71 },
72 ...(hideLabel
73 ? {}
74 : {
75 '& svg': {
76 margin: '0 6px 0 -4px',
77 },
78 }),
79 };
80});
81
82function PaneButtons({
83 themeStore,
84 hideLabel,
85}: {
86 themeStore: ThemeStore;
87 hideLabel?: boolean;
88}): JSX.Element {
89 return (
90 <PaneButtonGroup
91 size={hideLabel ? 'small' : 'medium'}
92 hideLabel={hideLabel ?? PaneButtons.defaultProps.hideLabel}
93 >
94 <ToggleButton
95 value="code"
96 selected={themeStore.showCode}
97 onClick={(event) => {
98 if (event.shiftKey || event.ctrlKey) {
99 themeStore.setSelectedPane('code');
100 } else {
101 themeStore.toggleCode();
102 }
103 }}
104 >
105 <CodeIcon fontSize="small" />
106 {!hideLabel && 'Code'}
107 </ToggleButton>
108 <ToggleButton
109 value="graph"
110 selected={themeStore.showGraph}
111 onClick={(event) => {
112 if (event.shiftKey || event.ctrlKey) {
113 themeStore.setSelectedPane('graph', event.shiftKey);
114 } else {
115 themeStore.toggleGraph();
116 }
117 }}
118 >
119 <SchemaRoundedIcon fontSize="small" />
120 {!hideLabel && 'Graph'}
121 </ToggleButton>
122 <ToggleButton
123 value="table"
124 selected={themeStore.showTable}
125 onClick={(event) => {
126 if (event.shiftKey || event.ctrlKey) {
127 themeStore.setSelectedPane('table', event.shiftKey);
128 } else {
129 themeStore.toggleTable();
130 }
131 }}
132 >
133 <TableChartIcon fontSize="small" />
134 {!hideLabel && 'Table'}
135 </ToggleButton>
136 </PaneButtonGroup>
137 );
138}
139
140PaneButtons.defaultProps = {
141 hideLabel: false,
142};
143
144export default observer(PaneButtons);
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx
index b5ff94e1..5ad16000 100644
--- a/subprojects/frontend/src/Refinery.tsx
+++ b/subprojects/frontend/src/Refinery.tsx
@@ -10,7 +10,7 @@ import { SnackbarProvider } from 'notistack';
10 10
11import TopBar from './TopBar'; 11import TopBar from './TopBar';
12import UpdateNotification from './UpdateNotification'; 12import UpdateNotification from './UpdateNotification';
13import EditorPane from './editor/EditorPane'; 13import WorkArea from './WorkArea';
14 14
15export default function Refinery(): JSX.Element { 15export default function Refinery(): JSX.Element {
16 return ( 16 return (
@@ -18,7 +18,7 @@ export default function Refinery(): JSX.Element {
18 <UpdateNotification /> 18 <UpdateNotification />
19 <Stack direction="column" height="100%" overflow="auto"> 19 <Stack direction="column" height="100%" overflow="auto">
20 <TopBar /> 20 <TopBar />
21 <EditorPane /> 21 <WorkArea />
22 </Stack> 22 </Stack>
23 </SnackbarProvider> 23 </SnackbarProvider>
24 ); 24 );
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx
index f2542b14..867a24a0 100644
--- a/subprojects/frontend/src/TopBar.tsx
+++ b/subprojects/frontend/src/TopBar.tsx
@@ -6,7 +6,6 @@
6 6
7import GitHubIcon from '@mui/icons-material/GitHub'; 7import GitHubIcon from '@mui/icons-material/GitHub';
8import AppBar from '@mui/material/AppBar'; 8import AppBar from '@mui/material/AppBar';
9import Button from '@mui/material/Button';
10import IconButton from '@mui/material/IconButton'; 9import IconButton from '@mui/material/IconButton';
11import Stack from '@mui/material/Stack'; 10import Stack from '@mui/material/Stack';
12import Toolbar from '@mui/material/Toolbar'; 11import Toolbar from '@mui/material/Toolbar';
@@ -17,6 +16,7 @@ import { throttle } from 'lodash-es';
17import { observer } from 'mobx-react-lite'; 16import { observer } from 'mobx-react-lite';
18import { useEffect, useMemo, useState } from 'react'; 17import { useEffect, useMemo, useState } from 'react';
19 18
19import PaneButtons from './PaneButtons';
20import { useRootStore } from './RootStoreProvider'; 20import { useRootStore } from './RootStoreProvider';
21import ToggleDarkModeButton from './ToggleDarkModeButton'; 21import ToggleDarkModeButton from './ToggleDarkModeButton';
22import GenerateButton from './editor/GenerateButton'; 22import GenerateButton from './editor/GenerateButton';
@@ -65,11 +65,12 @@ const DevModeBadge = styled('div')(({ theme }) => ({
65})); 65}));
66 66
67export default observer(function TopBar(): JSX.Element { 67export default observer(function TopBar(): JSX.Element {
68 const { editorStore } = useRootStore(); 68 const { editorStore, themeStore } = useRootStore();
69 const overlayVisible = useWindowControlsOverlayVisible(); 69 const overlayVisible = useWindowControlsOverlayVisible();
70 const { breakpoints } = useTheme(); 70 const { breakpoints } = useTheme();
71 const small = useMediaQuery(breakpoints.down('sm')); 71 const medium = useMediaQuery(breakpoints.up('sm'));
72 const large = useMediaQuery(breakpoints.up('md')); 72 const large = useMediaQuery(breakpoints.up('md'));
73 const veryLarge = useMediaQuery(breakpoints.up('lg'));
73 74
74 return ( 75 return (
75 <AppBar 76 <AppBar
@@ -100,50 +101,46 @@ export default observer(function TopBar(): JSX.Element {
100 py: 0.5, 101 py: 0.5,
101 }} 102 }}
102 > 103 >
103 <Typography variant="h6" component="h1" flexGrow={1}> 104 <Typography variant="h6" component="h1">
104 Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>} 105 Refinery {import.meta.env.DEV && <DevModeBadge>Dev</DevModeBadge>}
105 </Typography> 106 </Typography>
106 <Stack direction="row" marginRight={1}> 107 <Stack direction="row" alignItems="center" flexGrow={1} marginLeft={1}>
107 <GenerateButton editorStore={editorStore} hideWarnings={small} /> 108 {medium && !large && (
109 <PaneButtons themeStore={themeStore} hideLabel />
110 )}
111 </Stack>
112 {large && (
113 <Stack
114 direction="row"
115 alignItems="center"
116 sx={{
117 position: 'absolute',
118 top: 0,
119 bottom: 0,
120 left: '50%',
121 transform: 'translateX(-50%)',
122 }}
123 >
124 <PaneButtons themeStore={themeStore} />
125 </Stack>
126 )}
127 <Stack
128 direction="row"
129 marginLeft={1}
130 marginRight={1}
131 gap={1}
132 alignItems="center"
133 >
134 <GenerateButton editorStore={editorStore} hideWarnings={!veryLarge} />
108 {large && ( 135 {large && (
109 <> 136 <IconButton
110 <Button 137 aria-label="GitHub"
111 arial-label="Budapest University of Technology and Economics, Critical Systems Research Group" 138 href="https://github.com/graphs4value/refinery"
112 className="rounded" 139 target="_blank"
113 color="inherit" 140 color="inherit"
114 href="https://ftsrg.mit.bme.hu" 141 >
115 target="_blank" 142 <GitHubIcon />
116 sx={{ marginLeft: 1 }} 143 </IconButton>
117 >
118 BME FTSRG
119 </Button>
120 <Button
121 aria-label="McGill University, Department of Electrical and Computer Engineering"
122 className="rounded"
123 color="inherit"
124 href="https://www.mcgill.ca/ece/daniel-varro"
125 target="_blank"
126 >
127 McGill ECE
128 </Button>
129 <Button
130 aria-label="2022 Amazon Research Awards recipent"
131 className="rounded"
132 color="inherit"
133 href="https://www.amazon.science/research-awards/recipients/daniel-varro-fall-2021"
134 target="_blank"
135 >
136 Amazon Science
137 </Button>
138 <IconButton
139 aria-label="GitHub"
140 href="https://github.com/graphs4value/refinery"
141 target="_blank"
142 color="inherit"
143 >
144 <GitHubIcon />
145 </IconButton>
146 </>
147 )} 144 )}
148 </Stack> 145 </Stack>
149 <ToggleDarkModeButton /> 146 <ToggleDarkModeButton />
diff --git a/subprojects/frontend/src/WorkArea.tsx b/subprojects/frontend/src/WorkArea.tsx
new file mode 100644
index 00000000..adb29a50
--- /dev/null
+++ b/subprojects/frontend/src/WorkArea.tsx
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { observer } from 'mobx-react-lite';
8
9import DirectionalSplitPane from './DirectionalSplitPane';
10import { useRootStore } from './RootStoreProvider';
11import EditorPane from './editor/EditorPane';
12import GraphPane from './graph/GraphPane';
13import TablePane from './table/TablePane';
14
15export default observer(function WorkArea(): JSX.Element {
16 const { themeStore } = useRootStore();
17
18 return (
19 <DirectionalSplitPane
20 primary={<EditorPane />}
21 secondary={
22 <DirectionalSplitPane
23 primary={<GraphPane />}
24 secondary={<TablePane />}
25 primaryOnly={!themeStore.showTable}
26 secondaryOnly={!themeStore.showGraph}
27 />
28 }
29 primaryOnly={!themeStore.showGraph && !themeStore.showTable}
30 secondaryOnly={!themeStore.showCode}
31 />
32 );
33});
diff --git a/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx b/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx
new file mode 100644
index 00000000..591a3600
--- /dev/null
+++ b/subprojects/frontend/src/editor/AnalysisErrorNotification.tsx
@@ -0,0 +1,74 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { reaction } from 'mobx';
8import { type SnackbarKey, useSnackbar } from 'notistack';
9import { useEffect, useState } from 'react';
10
11import type EditorStore from './EditorStore';
12
13function MessageObserver({
14 editorStore,
15}: {
16 editorStore: EditorStore;
17}): React.ReactNode {
18 const [message, setMessage] = useState(
19 editorStore.delayedErrors.semanticsError ?? '',
20 );
21 // Instead of making this component an `observer`,
22 // we only update the message is one is present to make sure that the
23 // disappear animation has a chance to complete.
24 useEffect(
25 () =>
26 reaction(
27 () => editorStore.delayedErrors.semanticsError,
28 (newMessage) => {
29 if (newMessage !== undefined) {
30 setMessage(newMessage);
31 }
32 },
33 { fireImmediately: false },
34 ),
35 [editorStore],
36 );
37 return message;
38}
39
40export default function AnalysisErrorNotification({
41 editorStore,
42}: {
43 editorStore: EditorStore;
44}): null {
45 const { enqueueSnackbar, closeSnackbar } = useSnackbar();
46 useEffect(() => {
47 let key: SnackbarKey | undefined;
48 const disposer = reaction(
49 () => editorStore.delayedErrors.semanticsError !== undefined,
50 (hasError) => {
51 if (hasError) {
52 if (key === undefined) {
53 key = enqueueSnackbar({
54 message: <MessageObserver editorStore={editorStore} />,
55 variant: 'error',
56 persist: true,
57 });
58 }
59 } else if (key !== undefined) {
60 closeSnackbar(key);
61 key = undefined;
62 }
63 },
64 { fireImmediately: true },
65 );
66 return () => {
67 disposer();
68 if (key !== undefined) {
69 closeSnackbar(key);
70 }
71 };
72 }, [editorStore, enqueueSnackbar, closeSnackbar]);
73 return null;
74}
diff --git a/subprojects/frontend/src/editor/AnimatedButton.tsx b/subprojects/frontend/src/editor/AnimatedButton.tsx
index dbbda618..24ec69be 100644
--- a/subprojects/frontend/src/editor/AnimatedButton.tsx
+++ b/subprojects/frontend/src/editor/AnimatedButton.tsx
@@ -48,7 +48,7 @@ export default function AnimatedButton({
48 onClick?: () => void; 48 onClick?: () => void;
49 color: 'error' | 'warning' | 'primary' | 'inherit'; 49 color: 'error' | 'warning' | 'primary' | 'inherit';
50 disabled?: boolean; 50 disabled?: boolean;
51 startIcon: JSX.Element; 51 startIcon?: JSX.Element;
52 sx?: SxProps<Theme> | undefined; 52 sx?: SxProps<Theme> | undefined;
53 children?: ReactNode; 53 children?: ReactNode;
54}): JSX.Element { 54}): JSX.Element {
@@ -79,7 +79,11 @@ export default function AnimatedButton({
79 className="rounded shaded" 79 className="rounded shaded"
80 disabled={disabled ?? false} 80 disabled={disabled ?? false}
81 startIcon={startIcon} 81 startIcon={startIcon}
82 width={width === undefined ? 'auto' : `calc(${width} + 50px)`} 82 width={
83 width === undefined
84 ? 'auto'
85 : `calc(${width} + ${startIcon === undefined ? 28 : 50}px)`
86 }
83 > 87 >
84 <Box 88 <Box
85 display="flex" 89 display="flex"
@@ -100,6 +104,7 @@ AnimatedButton.defaultProps = {
100 'aria-label': undefined, 104 'aria-label': undefined,
101 onClick: undefined, 105 onClick: undefined,
102 disabled: false, 106 disabled: false,
107 startIcon: undefined,
103 sx: undefined, 108 sx: undefined,
104 children: undefined, 109 children: undefined,
105}; 110};
diff --git a/subprojects/frontend/src/editor/DiagnosticValue.ts b/subprojects/frontend/src/editor/DiagnosticValue.ts
index 20478262..410a46b7 100644
--- a/subprojects/frontend/src/editor/DiagnosticValue.ts
+++ b/subprojects/frontend/src/editor/DiagnosticValue.ts
@@ -14,6 +14,7 @@ export default class DiagnosticValue extends RangeValue {
14 error: new DiagnosticValue('error'), 14 error: new DiagnosticValue('error'),
15 warning: new DiagnosticValue('warning'), 15 warning: new DiagnosticValue('warning'),
16 info: new DiagnosticValue('info'), 16 info: new DiagnosticValue('info'),
17 hint: new DiagnosticValue('hint'),
17 }; 18 };
18 19
19 private constructor(public readonly severity: Severity) { 20 private constructor(public readonly severity: Severity) {
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index 9b187e5c..ca51f975 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -5,8 +5,8 @@
5 */ 5 */
6 6
7import type { Diagnostic } from '@codemirror/lint'; 7import type { Diagnostic } from '@codemirror/lint';
8import CancelIcon from '@mui/icons-material/Cancel';
8import CheckIcon from '@mui/icons-material/Check'; 9import CheckIcon from '@mui/icons-material/Check';
9import ErrorIcon from '@mui/icons-material/Error';
10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; 10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
11import FormatPaint from '@mui/icons-material/FormatPaint'; 11import FormatPaint from '@mui/icons-material/FormatPaint';
12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; 12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
@@ -28,7 +28,7 @@ import type EditorStore from './EditorStore';
28function getLintIcon(severity: Diagnostic['severity'] | undefined) { 28function getLintIcon(severity: Diagnostic['severity'] | undefined) {
29 switch (severity) { 29 switch (severity) {
30 case 'error': 30 case 'error':
31 return <ErrorIcon fontSize="small" />; 31 return <CancelIcon fontSize="small" />;
32 case 'warning': 32 case 'warning':
33 return <WarningIcon fontSize="small" />; 33 return <WarningIcon fontSize="small" />;
34 case 'info': 34 case 'info':
@@ -95,7 +95,7 @@ export default observer(function EditorButtons({
95 })} 95 })}
96 value="show-lint-panel" 96 value="show-lint-panel"
97 > 97 >
98 {getLintIcon(editorStore?.highestDiagnosticLevel)} 98 {getLintIcon(editorStore?.delayedErrors?.highestDiagnosticLevel)}
99 </ToggleButton> 99 </ToggleButton>
100 </ToggleButtonGroup> 100 </ToggleButtonGroup>
101 <IconButton 101 <IconButton
diff --git a/subprojects/frontend/src/editor/EditorErrors.tsx b/subprojects/frontend/src/editor/EditorErrors.tsx
new file mode 100644
index 00000000..40becf7e
--- /dev/null
+++ b/subprojects/frontend/src/editor/EditorErrors.tsx
@@ -0,0 +1,93 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { Diagnostic } from '@codemirror/lint';
8import { type IReactionDisposer, makeAutoObservable, reaction } from 'mobx';
9
10import type EditorStore from './EditorStore';
11
12const HYSTERESIS_TIME_MS = 250;
13
14export interface State {
15 analyzing: boolean;
16 errorCount: number;
17 warningCount: number;
18 infoCount: number;
19 semanticsError: string | undefined;
20}
21
22export default class EditorErrors implements State {
23 private readonly disposer: IReactionDisposer;
24
25 private timer: number | undefined;
26
27 analyzing = false;
28
29 errorCount = 0;
30
31 warningCount = 0;
32
33 infoCount = 0;
34
35 semanticsError: string | undefined;
36
37 constructor(private readonly store: EditorStore) {
38 this.updateImmediately(this.getNextState());
39 makeAutoObservable<EditorErrors, 'disposer' | 'timer'>(this, {
40 disposer: false,
41 timer: false,
42 });
43 this.disposer = reaction(
44 () => this.getNextState(),
45 (nextState) => {
46 if (this.timer !== undefined) {
47 clearTimeout(this.timer);
48 this.timer = undefined;
49 }
50 if (nextState.analyzing) {
51 this.timer = setTimeout(
52 () => this.updateImmediately(nextState),
53 HYSTERESIS_TIME_MS,
54 );
55 } else {
56 this.updateImmediately(nextState);
57 }
58 },
59 { fireImmediately: true },
60 );
61 }
62
63 get highestDiagnosticLevel(): Diagnostic['severity'] | undefined {
64 if (this.errorCount > 0) {
65 return 'error';
66 }
67 if (this.warningCount > 0) {
68 return 'warning';
69 }
70 if (this.infoCount > 0) {
71 return 'info';
72 }
73 return undefined;
74 }
75
76 private getNextState(): State {
77 return {
78 analyzing: this.store.analyzing,
79 errorCount: this.store.errorCount,
80 warningCount: this.store.warningCount,
81 infoCount: this.store.infoCount,
82 semanticsError: this.store.semanticsError,
83 };
84 }
85
86 private updateImmediately(nextState: State) {
87 Object.assign(this, nextState);
88 }
89
90 dispose() {
91 this.disposer();
92 }
93}
diff --git a/subprojects/frontend/src/editor/EditorPane.tsx b/subprojects/frontend/src/editor/EditorPane.tsx
index 87f408fe..1125a0ec 100644
--- a/subprojects/frontend/src/editor/EditorPane.tsx
+++ b/subprojects/frontend/src/editor/EditorPane.tsx
@@ -13,6 +13,7 @@ import { useState } from 'react';
13 13
14import { useRootStore } from '../RootStoreProvider'; 14import { useRootStore } from '../RootStoreProvider';
15 15
16import AnalysisErrorNotification from './AnalysisErrorNotification';
16import ConnectionStatusNotification from './ConnectionStatusNotification'; 17import ConnectionStatusNotification from './ConnectionStatusNotification';
17import EditorArea from './EditorArea'; 18import EditorArea from './EditorArea';
18import EditorButtons from './EditorButtons'; 19import EditorButtons from './EditorButtons';
@@ -39,7 +40,7 @@ export default observer(function EditorPane(): JSX.Element {
39 const { editorStore } = useRootStore(); 40 const { editorStore } = useRootStore();
40 41
41 return ( 42 return (
42 <Stack direction="column" flexGrow={1} flexShrink={1} overflow="auto"> 43 <Stack direction="column" height="100%" overflow="auto">
43 <Toolbar variant="dense"> 44 <Toolbar variant="dense">
44 <EditorButtons editorStore={editorStore} /> 45 <EditorButtons editorStore={editorStore} />
45 </Toolbar> 46 </Toolbar>
@@ -48,6 +49,7 @@ export default observer(function EditorPane(): JSX.Element {
48 <EditorLoading /> 49 <EditorLoading />
49 ) : ( 50 ) : (
50 <> 51 <>
52 <AnalysisErrorNotification editorStore={editorStore} />
51 <ConnectionStatusNotification editorStore={editorStore} /> 53 <ConnectionStatusNotification editorStore={editorStore} />
52 <SearchPanelPortal editorStore={editorStore} /> 54 <SearchPanelPortal editorStore={editorStore} />
53 <EditorArea editorStore={editorStore} /> 55 <EditorArea editorStore={editorStore} />
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index b98f085e..b5989ad1 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -26,9 +26,12 @@ import { makeAutoObservable, observable, runInAction } from 'mobx';
26import { nanoid } from 'nanoid'; 26import { nanoid } from 'nanoid';
27 27
28import type PWAStore from '../PWAStore'; 28import type PWAStore from '../PWAStore';
29import GraphStore from '../graph/GraphStore';
29import getLogger from '../utils/getLogger'; 30import getLogger from '../utils/getLogger';
30import type XtextClient from '../xtext/XtextClient'; 31import type XtextClient from '../xtext/XtextClient';
32import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults';
31 33
34import EditorErrors from './EditorErrors';
32import LintPanelStore from './LintPanelStore'; 35import LintPanelStore from './LintPanelStore';
33import SearchPanelStore from './SearchPanelStore'; 36import SearchPanelStore from './SearchPanelStore';
34import createEditorState from './createEditorState'; 37import createEditorState from './createEditorState';
@@ -54,13 +57,22 @@ export default class EditorStore {
54 57
55 readonly lintPanel: LintPanelStore; 58 readonly lintPanel: LintPanelStore;
56 59
60 readonly delayedErrors: EditorErrors;
61
57 showLineNumbers = false; 62 showLineNumbers = false;
58 63
59 disposed = false; 64 disposed = false;
60 65
66 analyzing = false;
67
68 semanticsError: string | undefined;
69
70 graph: GraphStore;
71
61 constructor(initialValue: string, pwaStore: PWAStore) { 72 constructor(initialValue: string, pwaStore: PWAStore) {
62 this.id = nanoid(); 73 this.id = nanoid();
63 this.state = createEditorState(initialValue, this); 74 this.state = createEditorState(initialValue, this);
75 this.delayedErrors = new EditorErrors(this);
64 this.searchPanel = new SearchPanelStore(this); 76 this.searchPanel = new SearchPanelStore(this);
65 this.lintPanel = new LintPanelStore(this); 77 this.lintPanel = new LintPanelStore(this);
66 (async () => { 78 (async () => {
@@ -75,6 +87,7 @@ export default class EditorStore {
75 })().catch((error) => { 87 })().catch((error) => {
76 log.error('Failed to load XtextClient', error); 88 log.error('Failed to load XtextClient', error);
77 }); 89 });
90 this.graph = new GraphStore();
78 makeAutoObservable<EditorStore, 'client'>(this, { 91 makeAutoObservable<EditorStore, 'client'>(this, {
79 id: false, 92 id: false,
80 state: observable.ref, 93 state: observable.ref,
@@ -213,19 +226,6 @@ export default class EditorStore {
213 this.doCommand(nextDiagnostic); 226 this.doCommand(nextDiagnostic);
214 } 227 }
215 228
216 get highestDiagnosticLevel(): Diagnostic['severity'] | undefined {
217 if (this.errorCount > 0) {
218 return 'error';
219 }
220 if (this.warningCount > 0) {
221 return 'warning';
222 }
223 if (this.infoCount > 0) {
224 return 'info';
225 }
226 return undefined;
227 }
228
229 updateSemanticHighlighting(ranges: IHighlightRange[]): void { 229 updateSemanticHighlighting(ranges: IHighlightRange[]): void {
230 this.dispatch(setSemanticHighlighting(ranges)); 230 this.dispatch(setSemanticHighlighting(ranges));
231 } 231 }
@@ -282,8 +282,29 @@ export default class EditorStore {
282 return true; 282 return true;
283 } 283 }
284 284
285 analysisStarted() {
286 this.analyzing = true;
287 }
288
289 analysisCompleted(semanticAnalysisSkipped = false) {
290 this.analyzing = false;
291 if (semanticAnalysisSkipped) {
292 this.semanticsError = undefined;
293 }
294 }
295
296 setSemanticsError(semanticsError: string) {
297 this.semanticsError = semanticsError;
298 }
299
300 setSemantics(semantics: SemanticsSuccessResult) {
301 this.semanticsError = undefined;
302 this.graph.setSemantics(semantics);
303 }
304
285 dispose(): void { 305 dispose(): void {
286 this.client?.dispose(); 306 this.client?.dispose();
307 this.delayedErrors.dispose();
287 this.disposed = true; 308 this.disposed = true;
288 } 309 }
289} 310}
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts
index e057ce18..055b62e2 100644
--- a/subprojects/frontend/src/editor/EditorTheme.ts
+++ b/subprojects/frontend/src/editor/EditorTheme.ts
@@ -4,15 +4,13 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import errorSVG from '@material-icons/svg/svg/error/baseline.svg?raw'; 7import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; 8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw';
9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; 9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw';
10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; 10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw';
11import { alpha, styled, type CSSObject } from '@mui/material/styles'; 11import { alpha, styled, type CSSObject } from '@mui/material/styles';
12 12
13function svgURL(svg: string): string { 13import svgURL from '../utils/svgURL';
14 return `url('data:image/svg+xml;utf8,${svg}')`;
15}
16 14
17export default styled('div', { 15export default styled('div', {
18 name: 'EditorTheme', 16 name: 'EditorTheme',
@@ -56,15 +54,16 @@ export default styled('div', {
56 '.cm-activeLineGutter': { 54 '.cm-activeLineGutter': {
57 background: 'transparent', 55 background: 'transparent',
58 }, 56 },
59 '.cm-cursor, .cm-cursor-primary': { 57 '.cm-cursor, .cm-dropCursor, .cm-cursor-primary': {
60 borderLeft: `2px solid ${theme.palette.info.main}`, 58 borderLeft: `2px solid ${theme.palette.info.main}`,
59 marginLeft: -1,
61 }, 60 },
62 '.cm-selectionBackground': { 61 '.cm-selectionBackground': {
63 background: theme.palette.highlight.selection, 62 background: theme.palette.highlight.selection,
64 }, 63 },
65 '.cm-focused': { 64 '.cm-focused': {
66 outline: 'none', 65 outline: 'none',
67 '.cm-selectionBackground': { 66 '& > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
68 background: theme.palette.highlight.selection, 67 background: theme.palette.highlight.selection,
69 }, 68 },
70 }, 69 },
@@ -106,7 +105,7 @@ export default styled('div', {
106 color: theme.palette.text.primary, 105 color: theme.palette.text.primary,
107 }, 106 },
108 }, 107 },
109 '.tok-problem-abstract, .tok-problem-new': { 108 '.tok-problem-abstract': {
110 fontStyle: 'italic', 109 fontStyle: 'italic',
111 }, 110 },
112 '.tok-problem-containment': { 111 '.tok-problem-containment': {
@@ -331,7 +330,7 @@ export default styled('div', {
331 '.cm-lintRange-active': { 330 '.cm-lintRange-active': {
332 background: theme.palette.highlight.activeLintRange, 331 background: theme.palette.highlight.activeLintRange,
333 }, 332 },
334 ...lintSeverityStyle('error', errorSVG, 120), 333 ...lintSeverityStyle('error', cancelSVG, 120),
335 ...lintSeverityStyle('warning', warningSVG, 110), 334 ...lintSeverityStyle('warning', warningSVG, 110),
336 ...lintSeverityStyle('info', infoSVG, 100), 335 ...lintSeverityStyle('info', infoSVG, 100),
337 }; 336 };
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx
index 3837ef8e..5bac0464 100644
--- a/subprojects/frontend/src/editor/GenerateButton.tsx
+++ b/subprojects/frontend/src/editor/GenerateButton.tsx
@@ -4,10 +4,8 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; 7import CancelIcon from '@mui/icons-material/Cancel';
8import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 8import PlayArrowIcon from '@mui/icons-material/PlayArrow';
9import Button from '@mui/material/Button';
10import type { SxProps, Theme } from '@mui/material/styles';
11import { observer } from 'mobx-react-lite'; 9import { observer } from 'mobx-react-lite';
12 10
13import AnimatedButton from './AnimatedButton'; 11import AnimatedButton from './AnimatedButton';
@@ -18,26 +16,45 @@ const GENERATE_LABEL = 'Generate';
18const GenerateButton = observer(function GenerateButton({ 16const GenerateButton = observer(function GenerateButton({
19 editorStore, 17 editorStore,
20 hideWarnings, 18 hideWarnings,
21 sx,
22}: { 19}: {
23 editorStore: EditorStore | undefined; 20 editorStore: EditorStore | undefined;
24 hideWarnings?: boolean | undefined; 21 hideWarnings?: boolean | undefined;
25 sx?: SxProps<Theme> | undefined;
26}): JSX.Element { 22}): JSX.Element {
27 if (editorStore === undefined) { 23 if (editorStore === undefined) {
28 return ( 24 return (
29 <Button 25 <AnimatedButton color="inherit" disabled>
30 color="inherit"
31 className="rounded shaded"
32 disabled
33 {...(sx === undefined ? {} : { sx })}
34 >
35 Loading&hellip; 26 Loading&hellip;
36 </Button> 27 </AnimatedButton>
28 );
29 }
30
31 const { analyzing, errorCount, warningCount, semanticsError } =
32 editorStore.delayedErrors;
33
34 if (analyzing) {
35 return (
36 <AnimatedButton color="inherit" disabled>
37 Analyzing&hellip;
38 </AnimatedButton>
37 ); 39 );
38 } 40 }
39 41
40 const { errorCount, warningCount } = editorStore; 42 if (semanticsError !== undefined && editorStore.opened) {
43 return (
44 <AnimatedButton
45 color="error"
46 disabled
47 startIcon={<CancelIcon />}
48 sx={(theme) => ({
49 '&.Mui-disabled': {
50 color: `${theme.palette.error.main} !important`,
51 },
52 })}
53 >
54 Analysis error
55 </AnimatedButton>
56 );
57 }
41 58
42 const diagnostics: string[] = []; 59 const diagnostics: string[] = [];
43 if (errorCount > 0) { 60 if (errorCount > 0) {
@@ -54,8 +71,7 @@ const GenerateButton = observer(function GenerateButton({
54 aria-label={`Select next diagnostic out of ${summary}`} 71 aria-label={`Select next diagnostic out of ${summary}`}
55 onClick={() => editorStore.nextDiagnostic()} 72 onClick={() => editorStore.nextDiagnostic()}
56 color="error" 73 color="error"
57 startIcon={<DangerousOutlinedIcon />} 74 startIcon={<CancelIcon />}
58 {...(sx === undefined ? {} : { sx })}
59 > 75 >
60 {summary} 76 {summary}
61 </AnimatedButton> 77 </AnimatedButton>
@@ -67,7 +83,6 @@ const GenerateButton = observer(function GenerateButton({
67 disabled={!editorStore.opened} 83 disabled={!editorStore.opened}
68 color={warningCount > 0 ? 'warning' : 'primary'} 84 color={warningCount > 0 ? 'warning' : 'primary'}
69 startIcon={<PlayArrowIcon />} 85 startIcon={<PlayArrowIcon />}
70 {...(sx === undefined ? {} : { sx })}
71 > 86 >
72 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`} 87 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`}
73 </AnimatedButton> 88 </AnimatedButton>
@@ -76,7 +91,6 @@ const GenerateButton = observer(function GenerateButton({
76 91
77GenerateButton.defaultProps = { 92GenerateButton.defaultProps = {
78 hideWarnings: false, 93 hideWarnings: false,
79 sx: undefined,
80}; 94};
81 95
82export default GenerateButton; 96export default GenerateButton;
diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
new file mode 100644
index 00000000..eec72a7d
--- /dev/null
+++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
@@ -0,0 +1,162 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import * as d3 from 'd3';
8import { type Graphviz, graphviz } from 'd3-graphviz';
9import type { BaseType, Selection } from 'd3-selection';
10import { reaction, type IReactionDisposer } from 'mobx';
11import { observer } from 'mobx-react-lite';
12import { useCallback, useRef, useState } from 'react';
13
14import getLogger from '../utils/getLogger';
15
16import type GraphStore from './GraphStore';
17import GraphTheme from './GraphTheme';
18import { FitZoomCallback } from './ZoomCanvas';
19import dotSource from './dotSource';
20import postProcessSvg from './postProcessSVG';
21
22const LOG = getLogger('graph.DotGraphVisualizer');
23
24function ptToPx(pt: number): number {
25 return (pt * 4) / 3;
26}
27
28function DotGraphVisualizer({
29 graph,
30 fitZoom,
31 transitionTime,
32 animateThreshold,
33}: {
34 graph: GraphStore;
35 fitZoom?: FitZoomCallback;
36 transitionTime?: number;
37 animateThreshold?: number;
38}): JSX.Element {
39 const transitionTimeOrDefault =
40 transitionTime ?? DotGraphVisualizer.defaultProps.transitionTime;
41 const animateThresholdOrDefault =
42 animateThreshold ?? DotGraphVisualizer.defaultProps.animateThreshold;
43 const disposerRef = useRef<IReactionDisposer | undefined>();
44 const graphvizRef = useRef<
45 Graphviz<BaseType, unknown, null, undefined> | undefined
46 >();
47 const [animate, setAnimate] = useState(true);
48
49 const setElement = useCallback(
50 (element: HTMLDivElement | null) => {
51 if (disposerRef.current !== undefined) {
52 disposerRef.current();
53 disposerRef.current = undefined;
54 }
55 if (graphvizRef.current !== undefined) {
56 // `@types/d3-graphviz` does not contain the signature for the `destroy` method.
57 (graphvizRef.current as unknown as { destroy(): void }).destroy();
58 graphvizRef.current = undefined;
59 }
60 if (element !== null) {
61 element.replaceChildren();
62 const renderer = graphviz(element) as Graphviz<
63 BaseType,
64 unknown,
65 null,
66 undefined
67 >;
68 renderer.keyMode('id');
69 ['TRUE', 'UNKNOWN', 'ERROR'].forEach((icon) =>
70 renderer.addImage(`#${icon}`, 16, 16),
71 );
72 renderer.zoom(false);
73 renderer.tweenPrecision('5%');
74 renderer.tweenShapes(false);
75 renderer.convertEqualSidedPolygons(false);
76 if (animate) {
77 const transition = () =>
78 d3
79 .transition()
80 .duration(transitionTimeOrDefault)
81 .ease(d3.easeCubic);
82 /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument,
83 @typescript-eslint/no-explicit-any --
84 Workaround for error in `@types/d3-graphviz`.
85 */
86 renderer.transition(transition as any);
87 } else {
88 renderer.tweenPaths(false);
89 }
90 let newViewBox = { width: 0, height: 0 };
91 renderer.onerror(LOG.error.bind(LOG));
92 renderer.on(
93 'postProcessSVG',
94 // @ts-expect-error Custom `d3-graphviz` hook not covered by typings.
95 (
96 svgSelection: Selection<SVGSVGElement, unknown, BaseType, unknown>,
97 ) => {
98 const svg = svgSelection.node();
99 if (svg !== null) {
100 postProcessSvg(svg);
101 newViewBox = {
102 width: ptToPx(svg.viewBox.baseVal.width),
103 height: ptToPx(svg.viewBox.baseVal.height),
104 };
105 } else {
106 // Do not trigger fit zoom.
107 newViewBox = { width: 0, height: 0 };
108 }
109 },
110 );
111 renderer.on('renderEnd', () => {
112 // `d3-graphviz` uses `<title>` elements for traceability,
113 // so we only remove them after the rendering is finished.
114 d3.select(element).selectAll('title').remove();
115 });
116 if (fitZoom !== undefined) {
117 if (animate) {
118 renderer.on('transitionStart', () => fitZoom(newViewBox));
119 } else {
120 renderer.on('end', () => fitZoom(false));
121 }
122 }
123 disposerRef.current = reaction(
124 () => dotSource(graph),
125 (result) => {
126 if (result === undefined) {
127 return;
128 }
129 const [source, size] = result;
130 // Disable tweening for large graphs to improve performance.
131 // See https://github.com/magjac/d3-graphviz/issues/232#issuecomment-1157555213
132 const newAnimate = size < animateThresholdOrDefault;
133 if (animate === newAnimate) {
134 renderer.renderDot(source);
135 } else {
136 setAnimate(newAnimate);
137 }
138 },
139 { fireImmediately: true },
140 );
141 graphvizRef.current = renderer;
142 }
143 },
144 [
145 graph,
146 fitZoom,
147 transitionTimeOrDefault,
148 animateThresholdOrDefault,
149 animate,
150 ],
151 );
152
153 return <GraphTheme ref={setElement} />;
154}
155
156DotGraphVisualizer.defaultProps = {
157 fitZoom: undefined,
158 transitionTime: 250,
159 animateThreshold: 100,
160};
161
162export default observer(DotGraphVisualizer);
diff --git a/subprojects/frontend/src/graph/GraphArea.tsx b/subprojects/frontend/src/graph/GraphArea.tsx
new file mode 100644
index 00000000..f8f40d22
--- /dev/null
+++ b/subprojects/frontend/src/graph/GraphArea.tsx
@@ -0,0 +1,54 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Box from '@mui/material/Box';
8import { useTheme } from '@mui/material/styles';
9import { observer } from 'mobx-react-lite';
10import { useResizeDetector } from 'react-resize-detector';
11
12import Loading from '../Loading';
13import { useRootStore } from '../RootStoreProvider';
14
15import DotGraphVisualizer from './DotGraphVisualizer';
16import VisibilityPanel from './VisibilityPanel';
17import ZoomCanvas from './ZoomCanvas';
18
19function GraphArea(): JSX.Element {
20 const { editorStore } = useRootStore();
21 const { breakpoints } = useTheme();
22 const { ref, width, height } = useResizeDetector({
23 refreshMode: 'debounce',
24 });
25
26 if (editorStore === undefined) {
27 return <Loading />;
28 }
29
30 const { graph } = editorStore;
31 const breakpoint = breakpoints.values.sm;
32 const dialog =
33 width === undefined ||
34 height === undefined ||
35 width < breakpoint ||
36 height < breakpoint;
37
38 return (
39 <Box
40 width="100%"
41 height="100%"
42 overflow="hidden"
43 position="relative"
44 ref={ref}
45 >
46 <ZoomCanvas>
47 {(fitZoom) => <DotGraphVisualizer graph={graph} fitZoom={fitZoom} />}
48 </ZoomCanvas>
49 <VisibilityPanel graph={graph} dialog={dialog} />
50 </Box>
51 );
52}
53
54export default observer(GraphArea);
diff --git a/subprojects/frontend/src/graph/GraphPane.tsx b/subprojects/frontend/src/graph/GraphPane.tsx
new file mode 100644
index 00000000..c2ef8927
--- /dev/null
+++ b/subprojects/frontend/src/graph/GraphPane.tsx
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Stack from '@mui/material/Stack';
8import { Suspense, lazy } from 'react';
9
10import Loading from '../Loading';
11
12const GraphArea = lazy(() => import('./GraphArea'));
13
14export default function GraphPane(): JSX.Element {
15 return (
16 <Stack
17 direction="column"
18 height="100%"
19 overflow="auto"
20 alignItems="center"
21 justifyContent="center"
22 >
23 <Suspense fallback={<Loading />}>
24 <GraphArea />
25 </Suspense>
26 </Stack>
27 );
28}
diff --git a/subprojects/frontend/src/graph/GraphStore.ts b/subprojects/frontend/src/graph/GraphStore.ts
new file mode 100644
index 00000000..ecb016b5
--- /dev/null
+++ b/subprojects/frontend/src/graph/GraphStore.ts
@@ -0,0 +1,187 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { makeAutoObservable, observable } from 'mobx';
8
9import type {
10 RelationMetadata,
11 SemanticsSuccessResult,
12} from '../xtext/xtextServiceResults';
13
14export type Visibility = 'all' | 'must' | 'none';
15
16export function getDefaultVisibility(
17 metadata: RelationMetadata | undefined,
18): Visibility {
19 if (metadata === undefined || metadata.arity <= 0 || metadata.arity > 2) {
20 return 'none';
21 }
22 const { detail } = metadata;
23 switch (detail.type) {
24 case 'class':
25 case 'reference':
26 case 'opposite':
27 return 'all';
28 case 'predicate':
29 return detail.error ? 'must' : 'none';
30 default:
31 return 'none';
32 }
33}
34
35export function isVisibilityAllowed(
36 metadata: RelationMetadata | undefined,
37 visibility: Visibility,
38): boolean {
39 if (metadata === undefined || metadata.arity <= 0 || metadata.arity > 2) {
40 return visibility === 'none';
41 }
42 const { detail } = metadata;
43 if (detail.type === 'predicate' && detail.error) {
44 // We can't display may matches of error predicates,
45 // because they have none by definition.
46 return visibility !== 'all';
47 }
48 return true;
49}
50
51export default class GraphStore {
52 semantics: SemanticsSuccessResult = {
53 nodes: [],
54 relations: [],
55 partialInterpretation: {},
56 };
57
58 relationMetadata = new Map<string, RelationMetadata>();
59
60 visibility = new Map<string, Visibility>();
61
62 abbreviate = true;
63
64 scopes = false;
65
66 selectedSymbol: RelationMetadata | undefined;
67
68 constructor() {
69 makeAutoObservable(this, {
70 semantics: observable.ref,
71 });
72 }
73
74 getVisibility(relation: string): Visibility {
75 const visibilityOverride = this.visibility.get(relation);
76 if (visibilityOverride !== undefined) {
77 return visibilityOverride;
78 }
79 return this.getDefaultVisibility(relation);
80 }
81
82 getDefaultVisibility(relation: string): Visibility {
83 const metadata = this.relationMetadata.get(relation);
84 return getDefaultVisibility(metadata);
85 }
86
87 isVisibilityAllowed(relation: string, visibility: Visibility): boolean {
88 const metadata = this.relationMetadata.get(relation);
89 return isVisibilityAllowed(metadata, visibility);
90 }
91
92 setVisibility(relation: string, visibility: Visibility): void {
93 const metadata = this.relationMetadata.get(relation);
94 if (metadata === undefined || !isVisibilityAllowed(metadata, visibility)) {
95 return;
96 }
97 const defaultVisiblity = getDefaultVisibility(metadata);
98 if (defaultVisiblity === visibility) {
99 this.visibility.delete(relation);
100 } else {
101 this.visibility.set(relation, visibility);
102 }
103 }
104
105 cycleVisibility(relation: string): void {
106 const metadata = this.relationMetadata.get(relation);
107 if (metadata === undefined) {
108 return;
109 }
110 switch (this.getVisibility(relation)) {
111 case 'none':
112 if (isVisibilityAllowed(metadata, 'must')) {
113 this.setVisibility(relation, 'must');
114 }
115 break;
116 case 'must':
117 {
118 const next = isVisibilityAllowed(metadata, 'all') ? 'all' : 'none';
119 this.setVisibility(relation, next);
120 }
121 break;
122 default:
123 this.setVisibility(relation, 'none');
124 break;
125 }
126 }
127
128 hideAll(): void {
129 this.relationMetadata.forEach((metadata, name) => {
130 if (getDefaultVisibility(metadata) === 'none') {
131 this.visibility.delete(name);
132 } else {
133 this.visibility.set(name, 'none');
134 }
135 });
136 }
137
138 resetFilter(): void {
139 this.visibility.clear();
140 }
141
142 getName({ name, simpleName }: { name: string; simpleName: string }): string {
143 return this.abbreviate ? simpleName : name;
144 }
145
146 toggleAbbrevaite(): void {
147 this.abbreviate = !this.abbreviate;
148 }
149
150 toggleScopes(): void {
151 this.scopes = !this.scopes;
152 }
153
154 setSelectedSymbol(option: RelationMetadata | undefined): void {
155 if (option === undefined) {
156 this.selectedSymbol = undefined;
157 return;
158 }
159 const metadata = this.relationMetadata.get(option.name);
160 if (metadata !== undefined) {
161 this.selectedSymbol = metadata;
162 } else {
163 this.selectedSymbol = undefined;
164 }
165 }
166
167 setSemantics(semantics: SemanticsSuccessResult) {
168 this.semantics = semantics;
169 this.relationMetadata.clear();
170 this.semantics.relations.forEach((metadata) => {
171 this.relationMetadata.set(metadata.name, metadata);
172 });
173 const toRemove = new Set<string>();
174 this.visibility.forEach((value, key) => {
175 if (
176 !this.isVisibilityAllowed(key, value) ||
177 this.getDefaultVisibility(key) === value
178 ) {
179 toRemove.add(key);
180 }
181 });
182 toRemove.forEach((key) => {
183 this.visibility.delete(key);
184 });
185 this.setSelectedSymbol(this.selectedSymbol);
186 }
187}
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx
new file mode 100644
index 00000000..989bd0c2
--- /dev/null
+++ b/subprojects/frontend/src/graph/GraphTheme.tsx
@@ -0,0 +1,120 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
8import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw';
9import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw';
10import { alpha, styled, type CSSObject } from '@mui/material/styles';
11
12import svgURL from '../utils/svgURL';
13
14function createEdgeColor(
15 suffix: string,
16 stroke: string,
17 fill?: string,
18): CSSObject {
19 return {
20 [`.edge-${suffix}`]: {
21 '& text': {
22 fill: stroke,
23 },
24 '& [stroke="black"]': {
25 stroke,
26 },
27 '& [fill="black"]': {
28 fill: fill ?? stroke,
29 },
30 },
31 };
32}
33
34export default styled('div', {
35 name: 'GraphTheme',
36})(({ theme }) => ({
37 '& svg': {
38 userSelect: 'none',
39 '.node': {
40 '& text': {
41 fontFamily: theme.typography.fontFamily,
42 fill: theme.palette.text.primary,
43 },
44 '& [stroke="black"]': {
45 stroke: theme.palette.text.primary,
46 },
47 '& [fill="green"]': {
48 fill:
49 theme.palette.mode === 'dark'
50 ? theme.palette.primary.dark
51 : theme.palette.primary.light,
52 },
53 '& [fill="white"]': {
54 fill: theme.palette.background.default,
55 },
56 },
57 '.node-INDIVIDUAL': {
58 '& [stroke="black"]': {
59 strokeWidth: 2,
60 },
61 },
62 '.node-shadow[fill="white"]': {
63 fill: alpha(
64 theme.palette.text.primary,
65 theme.palette.mode === 'dark' ? 0.32 : 0.24,
66 ),
67 },
68 '.node-exists-UNKNOWN [stroke="black"]': {
69 strokeDasharray: '5 2',
70 },
71 '.node-exists-FALSE': {
72 '& [fill="green"]': {
73 fill: theme.palette.background.default,
74 },
75 '& [stroke="black"]': {
76 strokeDasharray: '1 3',
77 stroke: theme.palette.text.secondary,
78 },
79 },
80 '.edge': {
81 '& text': {
82 fontFamily: theme.typography.fontFamily,
83 fill: theme.palette.text.primary,
84 },
85 '& [stroke="black"]': {
86 stroke: theme.palette.text.primary,
87 },
88 '& [fill="black"]': {
89 fill: theme.palette.text.primary,
90 },
91 },
92 ...createEdgeColor('UNKNOWN', theme.palette.text.secondary, 'none'),
93 ...createEdgeColor('ERROR', theme.palette.error.main),
94 '.icon': {
95 maskSize: '12px 12px',
96 maskPosition: '50% 50%',
97 maskRepeat: 'no-repeat',
98 width: '100%',
99 height: '100%',
100 },
101 '.icon-TRUE': {
102 maskImage: svgURL(labelSVG),
103 background: theme.palette.text.primary,
104 },
105 '.icon-UNKNOWN': {
106 maskImage: svgURL(labelOutlinedSVG),
107 background: theme.palette.text.secondary,
108 },
109 '.icon-ERROR': {
110 maskImage: svgURL(cancelSVG),
111 background: theme.palette.error.main,
112 },
113 'text.label-UNKNOWN': {
114 fill: theme.palette.text.secondary,
115 },
116 'text.label-ERROR': {
117 fill: theme.palette.error.main,
118 },
119 },
120}));
diff --git a/subprojects/frontend/src/graph/RelationName.tsx b/subprojects/frontend/src/graph/RelationName.tsx
new file mode 100644
index 00000000..ec26fb21
--- /dev/null
+++ b/subprojects/frontend/src/graph/RelationName.tsx
@@ -0,0 +1,72 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { styled } from '@mui/material/styles';
8import { observer } from 'mobx-react-lite';
9
10import { RelationMetadata } from '../xtext/xtextServiceResults';
11
12const Error = styled('span', {
13 name: 'RelationName-Error',
14})(({ theme }) => ({
15 color: theme.palette.error.main,
16}));
17
18const Qualifier = styled('span', {
19 name: 'RelationName-Qualifier',
20})(({ theme }) => ({
21 color: theme.palette.text.secondary,
22}));
23
24const FormattedName = observer(function FormattedName({
25 name,
26 metadata,
27}: {
28 name: string;
29 metadata: RelationMetadata;
30}): React.ReactNode {
31 const { detail } = metadata;
32 if (detail.type === 'class' && detail.abstractClass) {
33 return <i>{name}</i>;
34 }
35 if (detail.type === 'reference' && detail.containment) {
36 return <b>{name}</b>;
37 }
38 if (detail.type === 'predicate' && detail.error) {
39 return <Error>{name}</Error>;
40 }
41 return name;
42});
43
44function RelationName({
45 metadata,
46 abbreviate,
47}: {
48 metadata: RelationMetadata;
49 abbreviate?: boolean;
50}): JSX.Element {
51 const { name, simpleName } = metadata;
52 if (abbreviate ?? RelationName.defaultProps.abbreviate) {
53 return <FormattedName name={simpleName} metadata={metadata} />;
54 }
55 if (name.endsWith(simpleName)) {
56 return (
57 <>
58 <Qualifier>
59 {name.substring(0, name.length - simpleName.length)}
60 </Qualifier>
61 <FormattedName name={simpleName} metadata={metadata} />
62 </>
63 );
64 }
65 return <FormattedName name={name} metadata={metadata} />;
66}
67
68RelationName.defaultProps = {
69 abbreviate: false,
70};
71
72export default observer(RelationName);
diff --git a/subprojects/frontend/src/graph/VisibilityDialog.tsx b/subprojects/frontend/src/graph/VisibilityDialog.tsx
new file mode 100644
index 00000000..f1fef28b
--- /dev/null
+++ b/subprojects/frontend/src/graph/VisibilityDialog.tsx
@@ -0,0 +1,315 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import CloseIcon from '@mui/icons-material/Close';
8import FilterListIcon from '@mui/icons-material/FilterList';
9import LabelIcon from '@mui/icons-material/Label';
10import LabelOutlinedIcon from '@mui/icons-material/LabelOutlined';
11import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
12import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
13import Button from '@mui/material/Button';
14import Checkbox from '@mui/material/Checkbox';
15import FormControlLabel from '@mui/material/FormControlLabel';
16import IconButton from '@mui/material/IconButton';
17import Switch from '@mui/material/Switch';
18import Typography from '@mui/material/Typography';
19import { styled } from '@mui/material/styles';
20import { observer } from 'mobx-react-lite';
21import { useId } from 'react';
22
23import type GraphStore from './GraphStore';
24import { isVisibilityAllowed } from './GraphStore';
25import RelationName from './RelationName';
26
27const VisibilityDialogRoot = styled('div', {
28 name: 'VisibilityDialog-Root',
29 shouldForwardProp: (propName) => propName !== 'dialog',
30})<{ dialog: boolean }>(({ theme, dialog }) => {
31 const overlayOpacity = dialog ? 0.16 : 0.09;
32 return {
33 maxHeight: '100%',
34 maxWidth: '100%',
35 overflow: 'hidden',
36 display: 'flex',
37 flexDirection: 'column',
38 '.VisibilityDialog-title': {
39 display: 'flex',
40 flexDirection: 'row',
41 alignItems: 'center',
42 padding: theme.spacing(1),
43 paddingLeft: theme.spacing(2),
44 borderBottom: `1px solid ${theme.palette.divider}`,
45 '& h2': {
46 flexGrow: 1,
47 },
48 '.MuiIconButton-root': {
49 flexGrow: 0,
50 flexShrink: 0,
51 marginLeft: theme.spacing(2),
52 },
53 },
54 '.MuiFormControlLabel-root': {
55 marginLeft: 0,
56 paddingTop: theme.spacing(1),
57 paddingLeft: theme.spacing(1),
58 '& + .MuiFormControlLabel-root': {
59 paddingTop: 0,
60 },
61 },
62 '.VisibilityDialog-scroll': {
63 display: 'flex',
64 flexDirection: 'column',
65 height: 'auto',
66 overflowX: 'hidden',
67 overflowY: 'auto',
68 margin: `0 ${theme.spacing(2)}`,
69 '& table': {
70 // We use flexbox instead of `display: table` to get proper text-overflow
71 // behavior for overly long relation names.
72 display: 'flex',
73 flexDirection: 'column',
74 },
75 '& thead, & tbody': {
76 display: 'flex',
77 flexDirection: 'column',
78 },
79 '& thead': {
80 position: 'sticky',
81 top: 0,
82 zIndex: 999,
83 backgroundColor: theme.palette.background.paper,
84 ...(theme.palette.mode === 'dark'
85 ? {
86 // In dark mode, MUI Paper gets a lighter overlay.
87 backgroundImage: `linear-gradient(
88 rgba(255, 255, 255, ${overlayOpacity}),
89 rgba(255, 255, 255, ${overlayOpacity})
90 )`,
91 }
92 : {}),
93 '& tr': {
94 height: '44px',
95 },
96 },
97 '& tr': {
98 display: 'flex',
99 flexDirection: 'row',
100 maxWidth: '100%',
101 },
102 '& tbody tr': {
103 transition: theme.transitions.create('background', {
104 duration: theme.transitions.duration.shortest,
105 }),
106 '&:hover': {
107 background: theme.palette.action.hover,
108 '@media (hover: none)': {
109 background: 'transparent',
110 },
111 },
112 },
113 '& th, & td': {
114 display: 'flex',
115 flexDirection: 'row',
116 alignItems: 'center',
117 justifyContent: 'center',
118 // Set width in advance, since we can't rely on `display: table-cell`.
119 width: '44px',
120 },
121 '& th:nth-of-type(3), & td:nth-of-type(3)': {
122 justifyContent: 'start',
123 paddingLeft: theme.spacing(1),
124 paddingRight: theme.spacing(2),
125 // Only let the last column grow or shrink.
126 flexGrow: 1,
127 flexShrink: 1,
128 // Compute the maximum available space in advance to let the text overflow.
129 maxWidth: 'calc(100% - 88px)',
130 width: 'min-content',
131 },
132 '& td:nth-of-type(3)': {
133 cursor: 'pointer',
134 userSelect: 'none',
135 WebkitTapHighlightColor: 'transparent',
136 },
137
138 '& thead th, .VisibilityDialog-custom tr:last-child td': {
139 borderBottom: `1px solid ${theme.palette.divider}`,
140 },
141 },
142 // Hack to apply `text-overflow`.
143 '.VisibilityDialog-nowrap': {
144 maxWidth: '100%',
145 overflow: 'hidden',
146 wordWrap: 'nowrap',
147 textOverflow: 'ellipsis',
148 },
149 '.VisibilityDialog-buttons': {
150 padding: theme.spacing(1),
151 display: 'flex',
152 flexDirection: 'row',
153 justifyContent: 'flex-end',
154 ...(dialog
155 ? {
156 marginTop: theme.spacing(1),
157 borderTop: `1px solid ${theme.palette.divider}`,
158 }
159 : {}),
160 },
161 '.VisibilityDialog-empty': {
162 display: 'flex',
163 flexDirection: 'column',
164 alignItems: 'center',
165 color: theme.palette.text.secondary,
166 },
167 '.VisibilityDialog-emptyIcon': {
168 fontSize: '6rem',
169 marginBottom: theme.spacing(1),
170 },
171 };
172});
173
174function VisibilityDialog({
175 graph,
176 close,
177 dialog,
178}: {
179 graph: GraphStore;
180 close: () => void;
181 dialog?: boolean;
182}): JSX.Element {
183 const titleId = useId();
184
185 const builtinRows: JSX.Element[] = [];
186 const rows: JSX.Element[] = [];
187 graph.relationMetadata.forEach((metadata, name) => {
188 if (!isVisibilityAllowed(metadata, 'must')) {
189 return;
190 }
191 const visibility = graph.getVisibility(name);
192 const row = (
193 <tr key={metadata.name}>
194 <td>
195 <Checkbox
196 checked={visibility !== 'none'}
197 aria-label={`Show true and error values of ${metadata.simpleName}`}
198 onClick={() =>
199 graph.setVisibility(name, visibility === 'none' ? 'must' : 'none')
200 }
201 />
202 </td>
203 <td>
204 <Checkbox
205 checked={visibility === 'all'}
206 disabled={!isVisibilityAllowed(metadata, 'all')}
207 aria-label={`Show all values of ${metadata.simpleName}`}
208 onClick={() =>
209 graph.setVisibility(name, visibility === 'all' ? 'must' : 'all')
210 }
211 />
212 </td>
213 <td onClick={() => graph.cycleVisibility(name)}>
214 <div className="VisibilityDialog-nowrap">
215 <RelationName metadata={metadata} abbreviate={graph.abbreviate} />
216 </div>
217 </td>
218 </tr>
219 );
220 if (name.startsWith('builtin::')) {
221 builtinRows.push(row);
222 } else {
223 rows.push(row);
224 }
225 });
226
227 const hasRows = rows.length > 0 || builtinRows.length > 0;
228
229 return (
230 <VisibilityDialogRoot
231 dialog={dialog ?? VisibilityDialog.defaultProps.dialog}
232 aria-labelledby={dialog ? titleId : undefined}
233 >
234 {dialog && (
235 <div className="VisibilityDialog-title">
236 <Typography variant="h6" component="h2" id={titleId}>
237 Customize view
238 </Typography>
239 <IconButton aria-label="Close" onClick={close}>
240 <CloseIcon />
241 </IconButton>
242 </div>
243 )}
244 <FormControlLabel
245 control={
246 <Switch
247 checked={!graph.abbreviate}
248 onClick={() => graph.toggleAbbrevaite()}
249 />
250 }
251 label="Fully qualified names"
252 />
253 <FormControlLabel
254 control={
255 <Switch checked={graph.scopes} onClick={() => graph.toggleScopes()} />
256 }
257 label="Object scopes"
258 />
259 <div className="VisibilityDialog-scroll">
260 {hasRows ? (
261 <table cellSpacing={0}>
262 <thead>
263 <tr>
264 <th>
265 <LabelIcon />
266 </th>
267 <th>
268 <LabelOutlinedIcon />
269 </th>
270 <th>Symbol</th>
271 </tr>
272 </thead>
273 <tbody className="VisibilityDialog-custom">{...rows}</tbody>
274 <tbody className="VisibilityDialog-builtin">{...builtinRows}</tbody>
275 </table>
276 ) : (
277 <div className="VisibilityDialog-empty">
278 <SentimentVeryDissatisfiedIcon
279 className="VisibilityDialog-emptyIcon"
280 fontSize="inherit"
281 />
282 <div>Partial model is empty</div>
283 </div>
284 )}
285 </div>
286 <div className="VisibilityDialog-buttons">
287 <Button
288 color="inherit"
289 onClick={() => graph.hideAll()}
290 startIcon={<VisibilityOffIcon />}
291 >
292 Hide all
293 </Button>
294 <Button
295 color="inherit"
296 onClick={() => graph.resetFilter()}
297 startIcon={<FilterListIcon />}
298 >
299 Reset filter
300 </Button>
301 {!dialog && (
302 <Button color="inherit" onClick={close}>
303 Close
304 </Button>
305 )}
306 </div>
307 </VisibilityDialogRoot>
308 );
309}
310
311VisibilityDialog.defaultProps = {
312 dialog: false,
313};
314
315export default observer(VisibilityDialog);
diff --git a/subprojects/frontend/src/graph/VisibilityPanel.tsx b/subprojects/frontend/src/graph/VisibilityPanel.tsx
new file mode 100644
index 00000000..20c4ffca
--- /dev/null
+++ b/subprojects/frontend/src/graph/VisibilityPanel.tsx
@@ -0,0 +1,91 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
8import TuneIcon from '@mui/icons-material/Tune';
9import Badge from '@mui/material/Badge';
10import Dialog from '@mui/material/Dialog';
11import IconButton from '@mui/material/IconButton';
12import Paper from '@mui/material/Paper';
13import Slide from '@mui/material/Slide';
14import { styled } from '@mui/material/styles';
15import { observer } from 'mobx-react-lite';
16import { useCallback, useId, useState } from 'react';
17
18import type GraphStore from './GraphStore';
19import VisibilityDialog from './VisibilityDialog';
20
21const VisibilityPanelRoot = styled('div', {
22 name: 'VisibilityPanel-Root',
23})(({ theme }) => ({
24 position: 'absolute',
25 padding: theme.spacing(1),
26 top: 0,
27 left: 0,
28 maxHeight: '100%',
29 maxWidth: '100%',
30 overflow: 'hidden',
31 display: 'flex',
32 flexDirection: 'column',
33 alignItems: 'start',
34 '.VisibilityPanel-drawer': {
35 overflow: 'hidden',
36 display: 'flex',
37 maxWidth: '100%',
38 margin: theme.spacing(1),
39 },
40}));
41
42function VisibilityPanel({
43 graph,
44 dialog,
45}: {
46 graph: GraphStore;
47 dialog: boolean;
48}): JSX.Element {
49 const id = useId();
50 const [showFilter, setShowFilter] = useState(false);
51 const close = useCallback(() => setShowFilter(false), []);
52
53 return (
54 <VisibilityPanelRoot>
55 <IconButton
56 role="switch"
57 aria-checked={showFilter}
58 aria-controls={dialog ? undefined : id}
59 aria-label="Show filter panel"
60 onClick={() => setShowFilter(!showFilter)}
61 >
62 <Badge
63 color="primary"
64 variant="dot"
65 invisible={graph.visibility.size === 0}
66 >
67 {showFilter && !dialog ? <ChevronLeftIcon /> : <TuneIcon />}
68 </Badge>
69 </IconButton>
70 {dialog ? (
71 <Dialog open={showFilter} onClose={close} maxWidth="xl">
72 <VisibilityDialog graph={graph} close={close} dialog />
73 </Dialog>
74 ) : (
75 <Slide
76 direction="right"
77 in={showFilter}
78 id={id}
79 mountOnEnter
80 unmountOnExit
81 >
82 <Paper className="VisibilityPanel-drawer" elevation={4}>
83 <VisibilityDialog graph={graph} close={close} />
84 </Paper>
85 </Slide>
86 )}
87 </VisibilityPanelRoot>
88 );
89}
90
91export default observer(VisibilityPanel);
diff --git a/subprojects/frontend/src/graph/ZoomButtons.tsx b/subprojects/frontend/src/graph/ZoomButtons.tsx
new file mode 100644
index 00000000..83938cf4
--- /dev/null
+++ b/subprojects/frontend/src/graph/ZoomButtons.tsx
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import AddIcon from '@mui/icons-material/Add';
8import CropFreeIcon from '@mui/icons-material/CropFree';
9import RemoveIcon from '@mui/icons-material/Remove';
10import IconButton from '@mui/material/IconButton';
11import Stack from '@mui/material/Stack';
12import ToggleButton from '@mui/material/ToggleButton';
13
14import type { ChangeZoomCallback, SetFitZoomCallback } from './ZoomCanvas';
15
16export default function ZoomButtons({
17 changeZoom,
18 fitZoom,
19 setFitZoom,
20}: {
21 changeZoom: ChangeZoomCallback;
22 fitZoom: boolean;
23 setFitZoom: SetFitZoomCallback;
24}): JSX.Element {
25 return (
26 <Stack
27 direction="column"
28 p={1}
29 sx={{ position: 'absolute', bottom: 0, right: 0 }}
30 >
31 <IconButton aria-label="Zoom in" onClick={() => changeZoom(2)}>
32 <AddIcon fontSize="small" />
33 </IconButton>
34 <IconButton aria-label="Zoom out" onClick={() => changeZoom(0.5)}>
35 <RemoveIcon fontSize="small" />
36 </IconButton>
37 <ToggleButton
38 value="show-replace"
39 selected={fitZoom}
40 onClick={() => setFitZoom(!fitZoom)}
41 aria-label="Fit screen"
42 size="small"
43 className="iconOnly"
44 >
45 <CropFreeIcon fontSize="small" />
46 </ToggleButton>
47 </Stack>
48 );
49}
diff --git a/subprojects/frontend/src/graph/ZoomCanvas.tsx b/subprojects/frontend/src/graph/ZoomCanvas.tsx
new file mode 100644
index 00000000..0254bc59
--- /dev/null
+++ b/subprojects/frontend/src/graph/ZoomCanvas.tsx
@@ -0,0 +1,224 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Box from '@mui/material/Box';
8import * as d3 from 'd3';
9import { zoom as d3Zoom } from 'd3-zoom';
10import React, { useCallback, useRef, useState } from 'react';
11import { useResizeDetector } from 'react-resize-detector';
12
13import ZoomButtons from './ZoomButtons';
14
15declare module 'd3-zoom' {
16 // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Redeclaring type parameters.
17 interface ZoomBehavior<ZoomRefElement extends Element, Datum> {
18 // `@types/d3-zoom` does not contain the `center` function, because it is
19 // only available as a pull request for `d3-zoom`.
20 center(callback: (event: MouseEvent | Touch) => [number, number]): this;
21
22 // Custom `centroid` method added via patch.
23 centroid(centroid: [number, number]): this;
24 }
25}
26
27interface Transform {
28 x: number;
29 y: number;
30 k: number;
31}
32
33export type ChangeZoomCallback = (factor: number) => void;
34
35export type SetFitZoomCallback = (fitZoom: boolean) => void;
36
37export type FitZoomCallback = ((newSize?: {
38 width: number;
39 height: number;
40}) => void) &
41 ((newSize: boolean) => void);
42
43export default function ZoomCanvas({
44 children,
45 fitPadding,
46 transitionTime,
47}: {
48 children?: React.ReactNode | ((fitZoom: FitZoomCallback) => React.ReactNode);
49 fitPadding?: number;
50 transitionTime?: number;
51}): JSX.Element {
52 const fitPaddingOrDefault = fitPadding ?? ZoomCanvas.defaultProps.fitPadding;
53 const transitionTimeOrDefault =
54 transitionTime ?? ZoomCanvas.defaultProps.transitionTime;
55
56 const canvasRef = useRef<HTMLDivElement | undefined>();
57 const elementRef = useRef<HTMLDivElement | undefined>();
58 const zoomRef = useRef<
59 d3.ZoomBehavior<HTMLDivElement, unknown> | undefined
60 >();
61 const [zoom, setZoom] = useState<Transform>({ x: 0, y: 0, k: 1 });
62 const [fitZoom, setFitZoom] = useState(true);
63 const fitZoomRef = useRef(fitZoom);
64
65 const makeTransition = useCallback(
66 (element: HTMLDivElement) =>
67 d3.select(element).transition().duration(transitionTimeOrDefault),
68 [transitionTimeOrDefault],
69 );
70
71 const fitZoomCallback = useCallback<FitZoomCallback>(
72 (newSize) => {
73 if (
74 !fitZoomRef.current ||
75 canvasRef.current === undefined ||
76 zoomRef.current === undefined ||
77 elementRef.current === undefined
78 ) {
79 return;
80 }
81 let width = 0;
82 let height = 0;
83 if (newSize === undefined || typeof newSize === 'boolean') {
84 const elementRect = elementRef.current.getBoundingClientRect();
85 const currentFactor = d3.zoomTransform(canvasRef.current).k;
86 width = elementRect.width / currentFactor;
87 height = elementRect.height / currentFactor;
88 } else {
89 ({ width, height } = newSize);
90 }
91 if (width === 0 || height === 0) {
92 return;
93 }
94 const canvasRect = canvasRef.current.getBoundingClientRect();
95 const factor = Math.min(
96 1.0,
97 (canvasRect.width - 2 * fitPaddingOrDefault) / width,
98 (canvasRect.height - 2 * fitPaddingOrDefault) / height,
99 );
100 const target =
101 newSize === false
102 ? d3.select(canvasRef.current)
103 : makeTransition(canvasRef.current);
104 zoomRef.current.transform(target, d3.zoomIdentity.scale(factor));
105 },
106 [fitPaddingOrDefault, makeTransition],
107 );
108
109 const setFitZoomCallback = useCallback<SetFitZoomCallback>(
110 (newFitZoom) => {
111 setFitZoom(newFitZoom);
112 fitZoomRef.current = newFitZoom;
113 if (newFitZoom) {
114 fitZoomCallback();
115 }
116 },
117 [fitZoomCallback],
118 );
119
120 const changeZoomCallback = useCallback<ChangeZoomCallback>(
121 (factor) => {
122 setFitZoomCallback(false);
123 if (canvasRef.current === undefined || zoomRef.current === undefined) {
124 return;
125 }
126 const zoomTransition = makeTransition(canvasRef.current);
127 const center: [number, number] = [0, 0];
128 zoomRef.current.scaleBy(zoomTransition, factor, center);
129 },
130 [makeTransition, setFitZoomCallback],
131 );
132
133 const onResize = useCallback(() => fitZoomCallback(), [fitZoomCallback]);
134
135 const { ref: resizeRef } = useResizeDetector({
136 onResize,
137 refreshMode: 'debounce',
138 refreshRate: transitionTimeOrDefault,
139 });
140
141 const setCanvas = useCallback(
142 (canvas: HTMLDivElement | null) => {
143 canvasRef.current = canvas ?? undefined;
144 resizeRef(canvas);
145 if (canvas === null) {
146 return;
147 }
148 const zoomBehavior = d3Zoom<HTMLDivElement, unknown>()
149 .duration(transitionTimeOrDefault)
150 .center((event) => {
151 const { width, height } = canvas.getBoundingClientRect();
152 const [x, y] = d3.pointer(event, canvas);
153 return [x - width / 2, y - height / 2];
154 })
155 .centroid([0, 0])
156 .scaleExtent([1 / 32, 8]);
157 zoomBehavior.on(
158 'zoom',
159 (event: d3.D3ZoomEvent<HTMLDivElement, unknown>) => {
160 setZoom(event.transform);
161 if (event.sourceEvent) {
162 setFitZoomCallback(false);
163 }
164 },
165 );
166 d3.select(canvas).call(zoomBehavior);
167 zoomRef.current = zoomBehavior;
168 },
169 [transitionTimeOrDefault, setFitZoomCallback, resizeRef],
170 );
171
172 return (
173 <Box
174 sx={{
175 width: '100%',
176 height: '100%',
177 position: 'relative',
178 overflow: 'hidden',
179 }}
180 >
181 <Box
182 sx={{
183 position: 'absolute',
184 overflow: 'hidden',
185 top: 0,
186 left: 0,
187 right: 0,
188 bottom: 0,
189 }}
190 ref={setCanvas}
191 >
192 <Box
193 sx={{
194 position: 'absolute',
195 top: '50%',
196 left: '50%',
197 transform: `
198 translate(${zoom.x}px, ${zoom.y}px)
199 scale(${zoom.k})
200 translate(-50%, -50%)
201 `,
202 transformOrigin: '0 0',
203 }}
204 ref={elementRef}
205 >
206 {typeof children === 'function'
207 ? children(fitZoomCallback)
208 : children}
209 </Box>
210 </Box>
211 <ZoomButtons
212 changeZoom={changeZoomCallback}
213 fitZoom={fitZoom}
214 setFitZoom={setFitZoomCallback}
215 />
216 </Box>
217 );
218}
219
220ZoomCanvas.defaultProps = {
221 children: undefined,
222 fitPadding: 8,
223 transitionTime: 250,
224};
diff --git a/subprojects/frontend/src/graph/dotSource.ts b/subprojects/frontend/src/graph/dotSource.ts
new file mode 100644
index 00000000..5e0b44c8
--- /dev/null
+++ b/subprojects/frontend/src/graph/dotSource.ts
@@ -0,0 +1,340 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import type {
8 NodeMetadata,
9 RelationMetadata,
10} from '../xtext/xtextServiceResults';
11
12import type GraphStore from './GraphStore';
13
14const EDGE_WEIGHT = 1;
15const CONTAINMENT_WEIGHT = 5;
16const UNKNOWN_WEIGHT_FACTOR = 0.5;
17
18function nodeName(graph: GraphStore, metadata: NodeMetadata): string {
19 const name = graph.getName(metadata);
20 switch (metadata.kind) {
21 case 'INDIVIDUAL':
22 return `<b>${name}</b>`;
23 default:
24 return name;
25 }
26}
27
28function relationName(graph: GraphStore, metadata: RelationMetadata): string {
29 const name = graph.getName(metadata);
30 const { detail } = metadata;
31 if (detail.type === 'class' && detail.abstractClass) {
32 return `<i>${name}</i>`;
33 }
34 if (detail.type === 'reference' && detail.containment) {
35 return `<b>${name}</b>`;
36 }
37 return name;
38}
39
40interface NodeData {
41 isolated: boolean;
42 exists: string;
43 equalsSelf: string;
44 unaryPredicates: Map<RelationMetadata, string>;
45 count: string;
46}
47
48function computeNodeData(graph: GraphStore): NodeData[] {
49 const {
50 semantics: { nodes, relations, partialInterpretation },
51 } = graph;
52
53 const nodeData = Array.from(Array(nodes.length)).map(() => ({
54 isolated: true,
55 exists: 'FALSE',
56 equalsSelf: 'FALSE',
57 unaryPredicates: new Map(),
58 count: '[0]',
59 }));
60
61 relations.forEach((relation) => {
62 const visibility = graph.getVisibility(relation.name);
63 if (visibility === 'none') {
64 return;
65 }
66 const { arity } = relation;
67 const interpretation = partialInterpretation[relation.name] ?? [];
68 interpretation.forEach((tuple) => {
69 const value = tuple[arity];
70 if (visibility !== 'all' && value === 'UNKNOWN') {
71 return;
72 }
73 for (let i = 0; i < arity; i += 1) {
74 const index = tuple[i];
75 if (typeof index === 'number') {
76 const data = nodeData[index];
77 if (data !== undefined) {
78 data.isolated = false;
79 if (arity === 1) {
80 data.unaryPredicates.set(relation, value);
81 }
82 }
83 }
84 }
85 });
86 });
87
88 partialInterpretation['builtin::exists']?.forEach(([index, value]) => {
89 if (typeof index === 'number' && typeof value === 'string') {
90 const data = nodeData[index];
91 if (data !== undefined) {
92 data.exists = value;
93 }
94 }
95 });
96
97 partialInterpretation['builtin::equals']?.forEach(([index, other, value]) => {
98 if (
99 typeof index === 'number' &&
100 index === other &&
101 typeof value === 'string'
102 ) {
103 const data = nodeData[index];
104 if (data !== undefined) {
105 data.equalsSelf = value;
106 }
107 }
108 });
109
110 partialInterpretation['builtin::count']?.forEach(([index, value]) => {
111 if (typeof index === 'number' && typeof value === 'string') {
112 const data = nodeData[index];
113 if (data !== undefined) {
114 data.count = value;
115 }
116 }
117 });
118
119 return nodeData;
120}
121
122function createNodes(graph: GraphStore, lines: string[]): void {
123 const nodeData = computeNodeData(graph);
124 const {
125 semantics: { nodes },
126 scopes,
127 } = graph;
128
129 nodes.forEach((node, i) => {
130 const data = nodeData[i];
131 if (data === undefined || data.isolated) {
132 return;
133 }
134 const classList = [
135 `node-${node.kind}`,
136 `node-exists-${data.exists}`,
137 `node-equalsSelf-${data.equalsSelf}`,
138 ];
139 if (data.unaryPredicates.size === 0) {
140 classList.push('node-empty');
141 }
142 const classes = classList.join(' ');
143 const name = nodeName(graph, node);
144 const border = node.kind === 'INDIVIDUAL' ? 2 : 1;
145 const count = scopes ? ` ${data.count}` : '';
146 lines.push(`n${i} [id="${node.name}", class="${classes}", label=<
147 <table border="${border}" cellborder="0" cellspacing="0" style="rounded" bgcolor="white">
148 <tr><td cellpadding="4.5" width="32" bgcolor="green">${name}${count}</td></tr>`);
149 if (data.unaryPredicates.size > 0) {
150 lines.push(
151 '<hr/><tr><td cellpadding="4.5"><table fixedsize="TRUE" align="left" border="0" cellborder="0" cellspacing="0" cellpadding="1.5">',
152 );
153 data.unaryPredicates.forEach((value, relation) => {
154 lines.push(
155 `<tr>
156 <td><img src="#${value}"/></td>
157 <td width="1.5"></td>
158 <td align="left" href="#${value}" id="${node.name},${
159 relation.name
160 },label">${relationName(graph, relation)}</td>
161 </tr>`,
162 );
163 });
164 lines.push('</table></td></tr>');
165 }
166 lines.push('</table>>]');
167 });
168}
169
170function compare(
171 a: readonly (number | string)[],
172 b: readonly number[],
173): number {
174 if (a.length !== b.length + 1) {
175 throw new Error('Tuple length mismatch');
176 }
177 for (let i = 0; i < b.length; i += 1) {
178 const aItem = a[i];
179 const bItem = b[i];
180 if (typeof aItem !== 'number' || typeof bItem !== 'number') {
181 throw new Error('Invalid tuple');
182 }
183 if (aItem < bItem) {
184 return -1;
185 }
186 if (aItem > bItem) {
187 return 1;
188 }
189 }
190 return 0;
191}
192
193function binarySerach(
194 tuples: readonly (readonly (number | string)[])[],
195 key: readonly number[],
196): string | undefined {
197 let lower = 0;
198 let upper = tuples.length - 1;
199 while (lower <= upper) {
200 const middle = Math.floor((lower + upper) / 2);
201 const tuple = tuples[middle];
202 if (tuple === undefined) {
203 throw new Error('Range error');
204 }
205 const result = compare(tuple, key);
206 if (result === 0) {
207 const found = tuple[key.length];
208 if (typeof found !== 'string') {
209 throw new Error('Invalid tuple value');
210 }
211 return found;
212 }
213 if (result < 0) {
214 lower = middle + 1;
215 } else {
216 // result > 0
217 upper = middle - 1;
218 }
219 }
220 return undefined;
221}
222
223function createRelationEdges(
224 graph: GraphStore,
225 relation: RelationMetadata,
226 showUnknown: boolean,
227 lines: string[],
228): void {
229 const {
230 semantics: { nodes, partialInterpretation },
231 } = graph;
232 const { detail } = relation;
233
234 let constraint: 'true' | 'false' = 'true';
235 let weight = EDGE_WEIGHT;
236 let penwidth = 1;
237 const name = graph.getName(relation);
238 let label = `"${name}"`;
239 if (detail.type === 'reference' && detail.containment) {
240 weight = CONTAINMENT_WEIGHT;
241 label = `<<b>${name}</b>>`;
242 penwidth = 2;
243 } else if (
244 detail.type === 'opposite' &&
245 graph.getVisibility(detail.opposite) !== 'none'
246 ) {
247 constraint = 'false';
248 weight = 0;
249 }
250
251 const tuples = partialInterpretation[relation.name] ?? [];
252 tuples.forEach(([from, to, value]) => {
253 const isUnknown = value === 'UNKNOWN';
254 if (
255 (!showUnknown && isUnknown) ||
256 typeof from !== 'number' ||
257 typeof to !== 'number' ||
258 typeof value !== 'string'
259 ) {
260 return;
261 }
262
263 const fromNode = nodes[from];
264 const toNode = nodes[to];
265 if (fromNode === undefined || toNode === undefined) {
266 return;
267 }
268
269 let dir = 'forward';
270 let edgeConstraint = constraint;
271 let edgeWeight = weight;
272 const opposite = binarySerach(tuples, [to, from]);
273 const oppositeUnknown = opposite === 'UNKNOWN';
274 const oppositeSet = opposite !== undefined;
275 const oppositeVisible = oppositeSet && (showUnknown || !oppositeUnknown);
276 if (opposite === value) {
277 if (to < from) {
278 // We already added this edge in the reverse direction.
279 return;
280 }
281 if (to > from) {
282 dir = 'both';
283 }
284 } else if (oppositeVisible && to < from) {
285 // Let the opposite edge drive the graph layout.
286 edgeConstraint = 'false';
287 edgeWeight = 0;
288 } else if (isUnknown && (!oppositeSet || oppositeUnknown)) {
289 // Only apply the UNKNOWN value penalty if we aren't the opposite
290 // edge driving the graph layout from above, or the penalty would
291 // be applied anyway.
292 edgeWeight *= UNKNOWN_WEIGHT_FACTOR;
293 }
294
295 lines.push(`n${from} -> n${to} [
296 id="${fromNode.name},${toNode.name},${relation.name}",
297 dir="${dir}",
298 constraint=${edgeConstraint},
299 weight=${edgeWeight},
300 xlabel=${label},
301 penwidth=${penwidth},
302 arrowsize=${penwidth >= 2 ? 0.875 : 1},
303 style="${isUnknown ? 'dashed' : 'solid'}",
304 class="edge-${value}"
305 ]`);
306 });
307}
308
309function createEdges(graph: GraphStore, lines: string[]): void {
310 const {
311 semantics: { relations },
312 } = graph;
313 relations.forEach((relation) => {
314 if (relation.arity !== 2) {
315 return;
316 }
317 const visibility = graph.getVisibility(relation.name);
318 if (visibility !== 'none') {
319 createRelationEdges(graph, relation, visibility === 'all', lines);
320 }
321 });
322}
323
324export default function dotSource(
325 graph: GraphStore | undefined,
326): [string, number] | undefined {
327 if (graph === undefined) {
328 return undefined;
329 }
330 const lines = [
331 'digraph {',
332 'graph [bgcolor=transparent];',
333 `node [fontsize=12, shape=plain, fontname="OpenSans"];`,
334 'edge [fontsize=10.5, color=black, fontname="OpenSans"];',
335 ];
336 createNodes(graph, lines);
337 createEdges(graph, lines);
338 lines.push('}');
339 return [lines.join('\n'), lines.length];
340}
diff --git a/subprojects/frontend/src/graph/parseBBox.ts b/subprojects/frontend/src/graph/parseBBox.ts
new file mode 100644
index 00000000..34df746b
--- /dev/null
+++ b/subprojects/frontend/src/graph/parseBBox.ts
@@ -0,0 +1,68 @@
1/*
2 * Copyright 2017, Magnus Jacobsson
3 * Copyright 2023, The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 *
7 * This file Incorporates patches from the Refinery authors.
8 *
9 * Redistribution and use is only permitted if neither
10 * the name of the copyright holder Magnus Jacobsson nor the names of other
11 * contributors to the d3-graphviz project are used to endorse or promote
12 * products derived from this software as per the 3rd clause of the
13 * 3-clause BSD license.
14 *
15 * See LICENSES/BSD-3-Clause.txt for more details.
16 */
17
18export interface BBox {
19 x: number;
20 y: number;
21 width: number;
22 height: number;
23}
24
25function parsePoints(points: string[]): BBox {
26 const x = points.map((p) => Number(p.split(',')[0] ?? 0));
27 const y = points.map((p) => Number(p.split(',')[1] ?? 0));
28 const xmin = Math.min.apply(null, x);
29 const xmax = Math.max.apply(null, x);
30 const ymin = Math.min.apply(null, y);
31 const ymax = Math.max.apply(null, y);
32 return {
33 x: xmin,
34 y: ymin,
35 width: xmax - xmin,
36 height: ymax - ymin,
37 };
38}
39
40/**
41 * Compute the bounding box of a polygon without adding it to the DOM.
42 *
43 * Copyed from
44 * https://github.com/magjac/d3-graphviz/blob/81ab523fe5189a90da2d9d9cc9015c7079eea780/src/element.js#L36-L53
45 *
46 * @param path The polygon to compute the bounding box of.
47 * @returns The computed bounding box.
48 */
49export function parsePolygonBBox(polygon: SVGPolygonElement): BBox {
50 const points = (polygon.getAttribute('points') ?? '').split(' ');
51 return parsePoints(points);
52}
53
54/**
55 * Compute the bounding box of a path without adding it to the DOM.
56 *
57 * Copyed from
58 * https://github.com/magjac/d3-graphviz/blob/81ab523fe5189a90da2d9d9cc9015c7079eea780/src/element.js#L56-L75
59 *
60 * @param path The path to compute the bounding box of.
61 * @returns The computed bounding box.
62 */
63export function parsePathBBox(path: SVGPathElement): BBox {
64 const d = path.getAttribute('d') ?? '';
65 const points = d.split(/[A-Z ]/);
66 points.shift();
67 return parsePoints(points);
68}
diff --git a/subprojects/frontend/src/graph/postProcessSVG.ts b/subprojects/frontend/src/graph/postProcessSVG.ts
new file mode 100644
index 00000000..a580f5c6
--- /dev/null
+++ b/subprojects/frontend/src/graph/postProcessSVG.ts
@@ -0,0 +1,186 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { type BBox, parsePolygonBBox, parsePathBBox } from './parseBBox';
8
9const SVG_NS = 'http://www.w3.org/2000/svg';
10const XLINK_NS = 'http://www.w3.org/1999/xlink';
11
12function modifyAttribute(element: Element, attribute: string, change: number) {
13 const valueString = element.getAttribute(attribute);
14 if (valueString === null) {
15 return;
16 }
17 const value = parseInt(valueString, 10);
18 element.setAttribute(attribute, String(value + change));
19}
20
21function addShadow(
22 node: SVGGElement,
23 container: SVGRectElement,
24 offset: number,
25): void {
26 const shadow = container.cloneNode() as SVGRectElement;
27 // Leave space for 1pt stroke around the original container.
28 const offsetWithStroke = offset - 0.5;
29 modifyAttribute(shadow, 'x', offsetWithStroke);
30 modifyAttribute(shadow, 'y', offsetWithStroke);
31 modifyAttribute(shadow, 'width', 1);
32 modifyAttribute(shadow, 'height', 1);
33 modifyAttribute(shadow, 'rx', 0.5);
34 modifyAttribute(shadow, 'ry', 0.5);
35 shadow.setAttribute('class', 'node-shadow');
36 shadow.id = `${node.id},shadow`;
37 node.insertBefore(shadow, node.firstChild);
38}
39
40function clipCompartmentBackground(node: SVGGElement) {
41 // Background rectangle of the node created by the `<table bgcolor="white">`
42 // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
43 const container = node.querySelector<SVGRectElement>('rect[fill="white"]');
44 // Background rectangle of the lower compartment created by the `<td bgcolor="green">`
45 // HTML element in dot. It was transformed into a rounded rect by `fixNodeBackground`.
46 // Since dot doesn't round the coners of `<td>` background,
47 // we have to clip it ourselves.
48 const compartment = node.querySelector<SVGRectElement>('rect[fill="green"]');
49 // Make sure we provide traceability with IDs also for the border.
50 const border = node.querySelector<SVGRectElement>('rect[stroke="black"]');
51 if (container === null || compartment === null || border === null) {
52 return;
53 }
54 const copyOfContainer = container.cloneNode() as SVGRectElement;
55 const clipPath = document.createElementNS(SVG_NS, 'clipPath');
56 const clipId = `${node.id},,clip`;
57 clipPath.setAttribute('id', clipId);
58 clipPath.appendChild(copyOfContainer);
59 node.appendChild(clipPath);
60 compartment.setAttribute('clip-path', `url(#${clipId})`);
61 // Enlarge the compartment to completely cover the background.
62 modifyAttribute(compartment, 'y', -5);
63 modifyAttribute(compartment, 'x', -5);
64 modifyAttribute(compartment, 'width', 10);
65 const isEmpty = node.classList.contains('node-empty');
66 // Make sure that empty nodes are fully filled.
67 modifyAttribute(compartment, 'height', isEmpty ? 10 : 5);
68 if (node.classList.contains('node-equalsSelf-UNKNOWN')) {
69 addShadow(node, container, 6);
70 }
71 container.id = `${node.id},container`;
72 compartment.id = `${node.id},compartment`;
73 border.id = `${node.id},border`;
74}
75
76function createRect(
77 { x, y, width, height }: BBox,
78 original: SVGElement,
79): SVGRectElement {
80 const rect = document.createElementNS(SVG_NS, 'rect');
81 rect.setAttribute('fill', original.getAttribute('fill') ?? '');
82 rect.setAttribute('stroke', original.getAttribute('stroke') ?? '');
83 rect.setAttribute('x', String(x));
84 rect.setAttribute('y', String(y));
85 rect.setAttribute('width', String(width));
86 rect.setAttribute('height', String(height));
87 return rect;
88}
89
90function optimizeNodeShapes(node: SVGGElement) {
91 node.querySelectorAll('path').forEach((path) => {
92 const bbox = parsePathBBox(path);
93 const rect = createRect(bbox, path);
94 rect.setAttribute('rx', '12');
95 rect.setAttribute('ry', '12');
96 path.parentNode?.replaceChild(rect, path);
97 });
98 node.querySelectorAll('polygon').forEach((polygon) => {
99 const bbox = parsePolygonBBox(polygon);
100 if (bbox.height === 0) {
101 const polyline = document.createElementNS(SVG_NS, 'polyline');
102 polyline.setAttribute('stroke', polygon.getAttribute('stroke') ?? '');
103 polyline.setAttribute(
104 'points',
105 `${bbox.x},${bbox.y} ${bbox.x + bbox.width},${bbox.y}`,
106 );
107 polygon.parentNode?.replaceChild(polyline, polygon);
108 } else {
109 const rect = createRect(bbox, polygon);
110 polygon.parentNode?.replaceChild(rect, polygon);
111 }
112 });
113 clipCompartmentBackground(node);
114}
115
116function hrefToClass(node: SVGGElement) {
117 node.querySelectorAll<SVGAElement>('a').forEach((a) => {
118 if (a.parentNode === null) {
119 return;
120 }
121 const href = a.getAttribute('href') ?? a.getAttributeNS(XLINK_NS, 'href');
122 if (href === 'undefined' || !href?.startsWith('#')) {
123 return;
124 }
125 while (a.lastChild !== null) {
126 const child = a.lastChild;
127 a.removeChild(child);
128 if (child.nodeType === Node.ELEMENT_NODE) {
129 const element = child as Element;
130 element.classList.add('label', `label-${href.replace('#', '')}`);
131 a.after(child);
132 }
133 }
134 a.parentNode.removeChild(a);
135 });
136}
137
138function replaceImages(node: SVGGElement) {
139 node.querySelectorAll<SVGImageElement>('image').forEach((image) => {
140 const href =
141 image.getAttribute('href') ?? image.getAttributeNS(XLINK_NS, 'href');
142 if (href === 'undefined' || !href?.startsWith('#')) {
143 return;
144 }
145 const width = image.getAttribute('width')?.replace('px', '') ?? '';
146 const height = image.getAttribute('height')?.replace('px', '') ?? '';
147 const foreign = document.createElementNS(SVG_NS, 'foreignObject');
148 foreign.setAttribute('x', image.getAttribute('x') ?? '');
149 foreign.setAttribute('y', image.getAttribute('y') ?? '');
150 foreign.setAttribute('width', width);
151 foreign.setAttribute('height', height);
152 const div = document.createElement('div');
153 div.classList.add('icon', `icon-${href.replace('#', '')}`);
154 foreign.appendChild(div);
155 const sibling = image.nextElementSibling;
156 // Since dot doesn't respect the `id` attribute on table cells with a single image,
157 // compute the ID based on the ID of the next element (the label).
158 if (
159 sibling !== null &&
160 sibling.tagName.toLowerCase() === 'g' &&
161 sibling.id !== ''
162 ) {
163 foreign.id = `${sibling.id},icon`;
164 }
165 image.parentNode?.replaceChild(foreign, image);
166 });
167}
168
169export default function postProcessSvg(svg: SVGSVGElement) {
170 // svg
171 // .querySelectorAll<SVGTitleElement>('title')
172 // .forEach((title) => title.parentElement?.removeChild(title));
173 svg.querySelectorAll<SVGGElement>('g.node').forEach((node) => {
174 optimizeNodeShapes(node);
175 hrefToClass(node);
176 replaceImages(node);
177 });
178 // Increase padding to fit box shadows for multi-objects.
179 const viewBox = [
180 svg.viewBox.baseVal.x - 6,
181 svg.viewBox.baseVal.y - 6,
182 svg.viewBox.baseVal.width + 12,
183 svg.viewBox.baseVal.height + 12,
184 ];
185 svg.setAttribute('viewBox', viewBox.join(' '));
186}
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx
index cb11e6c3..e8a22e82 100644
--- a/subprojects/frontend/src/index.tsx
+++ b/subprojects/frontend/src/index.tsx
@@ -4,45 +4,46 @@
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import { styled } from '@mui/material/styles';
7import { configure } from 'mobx'; 8import { configure } from 'mobx';
8import { type Root, createRoot } from 'react-dom/client'; 9import { type Root, createRoot } from 'react-dom/client';
9 10
10import App from './App'; 11import App from './App';
11import RootStore from './RootStore'; 12import RootStore from './RootStore';
12 13
13const initialValue = `// Metamodel 14// Make sure `styled` ends up in the entry chunk.
15// https://github.com/mui/material-ui/issues/32727#issuecomment-1659945548
16(window as unknown as { fixViteIssue: unknown }).fixViteIssue = styled;
17
18const initialValue = `% Metamodel
14class Person { 19class Person {
20 contains Post[] posts opposite author
15 Person[] friend opposite friend 21 Person[] friend opposite friend
16} 22}
17 23
18class Post { 24class Post {
19 Person author 25 container Person author opposite posts
20 Post[0..1] replyTo 26 Post replyTo
21} 27}
22 28
23// Constraints 29% Constraints
24error replyToNotFriend(Post x, Post y) <-> 30error replyToNotFriend(Post x, Post y) <->
25 replyTo(x, y), 31 replyTo(x, y),
26 author(x, xAuthor), 32 author(x, xAuthor),
27 author(y, yAuthor), 33 author(y, yAuthor),
34 xAuthor != yAuthor,
28 !friend(xAuthor, yAuthor). 35 !friend(xAuthor, yAuthor).
29 36
30error replyToCycle(Post x) <-> replyTo+(x,x). 37error replyToCycle(Post x) <-> replyTo+(x, x).
31 38
32// Instance model 39% Instance model
33Person(a).
34Person(b).
35friend(a, b). 40friend(a, b).
36friend(b, a).
37Post(p1).
38author(p1, a). 41author(p1, a).
39Post(p2).
40author(p2, b). 42author(p2, b).
41replyTo(p2, p1).
42 43
43!author(Post::new, a). // Automatically inferred: author(Post::new, b). 44!author(Post::new, a).
44 45
45// Scope 46% Scope
46scope Post = 10..15, Person += 0. 47scope Post = 10..15, Person += 0.
47`; 48`;
48 49
diff --git a/subprojects/frontend/src/language/indentation.ts b/subprojects/frontend/src/language/indentation.ts
index 8446d7fa..6806147b 100644
--- a/subprojects/frontend/src/language/indentation.ts
+++ b/subprojects/frontend/src/language/indentation.ts
@@ -2,7 +2,7 @@
2 * Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others 2 * Copyright (C) 2018-2021 by Marijn Haverbeke <marijnh@gmail.com> and others
3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT AND EPL-2.0
6 */ 6 */
7 7
8import type { TreeIndentContext } from '@codemirror/language'; 8import type { TreeIndentContext } from '@codemirror/language';
diff --git a/subprojects/frontend/src/table/RelationGrid.tsx b/subprojects/frontend/src/table/RelationGrid.tsx
new file mode 100644
index 00000000..004982c9
--- /dev/null
+++ b/subprojects/frontend/src/table/RelationGrid.tsx
@@ -0,0 +1,109 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Box from '@mui/material/Box';
8import {
9 DataGrid,
10 type GridRenderCellParams,
11 type GridColDef,
12} from '@mui/x-data-grid';
13import { observer } from 'mobx-react-lite';
14import { useMemo } from 'react';
15
16import type GraphStore from '../graph/GraphStore';
17
18import TableToolbar from './TableToolbar';
19import ValueRenderer from './ValueRenderer';
20
21interface Row {
22 nodes: string[];
23 value: string;
24}
25
26function RelationGrid({ graph }: { graph: GraphStore }): JSX.Element {
27 const {
28 selectedSymbol,
29 semantics: { nodes, partialInterpretation },
30 } = graph;
31 const symbolName = selectedSymbol?.name;
32 const arity = selectedSymbol?.arity ?? 0;
33
34 const columns = useMemo<GridColDef<Row>[]>(() => {
35 const defs: GridColDef<Row>[] = [];
36 for (let i = 0; i < arity; i += 1) {
37 defs.push({
38 field: `n${i}`,
39 headerName: String(i + 1),
40 valueGetter: (row) => row.row.nodes[i] ?? '',
41 flex: 1,
42 });
43 }
44 defs.push({
45 field: 'value',
46 headerName: 'Value',
47 flex: 1,
48 renderCell: ({ value }: GridRenderCellParams<Row, string>) => (
49 <ValueRenderer value={value} />
50 ),
51 });
52 return defs;
53 }, [arity]);
54
55 const rows = useMemo<Row[]>(() => {
56 if (symbolName === undefined) {
57 return [];
58 }
59 const interpretation = partialInterpretation[symbolName] ?? [];
60 return interpretation.map((tuple) => {
61 const nodeNames: string[] = [];
62 for (let i = 0; i < arity; i += 1) {
63 const index = tuple[i];
64 if (typeof index === 'number') {
65 const node = nodes[index];
66 if (node !== undefined) {
67 nodeNames.push(node.name);
68 }
69 }
70 }
71 return {
72 nodes: nodeNames,
73 value: String(tuple[arity]),
74 };
75 });
76 }, [arity, nodes, partialInterpretation, symbolName]);
77
78 return (
79 <Box
80 width="100%"
81 height="100%"
82 p={1}
83 sx={(theme) => ({
84 '.MuiDataGrid-withBorderColor': {
85 borderColor:
86 theme.palette.mode === 'dark'
87 ? theme.palette.divider
88 : theme.palette.outer.border,
89 },
90 })}
91 >
92 <DataGrid
93 slots={{ toolbar: TableToolbar }}
94 slotProps={{
95 toolbar: {
96 graph,
97 },
98 }}
99 density="compact"
100 rowSelection={false}
101 columns={columns}
102 rows={rows}
103 getRowId={(row) => row.nodes.join(',')}
104 />
105 </Box>
106 );
107}
108
109export default observer(RelationGrid);
diff --git a/subprojects/frontend/src/table/SymbolSelector.tsx b/subprojects/frontend/src/table/SymbolSelector.tsx
new file mode 100644
index 00000000..5272f8ed
--- /dev/null
+++ b/subprojects/frontend/src/table/SymbolSelector.tsx
@@ -0,0 +1,65 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Autocomplete from '@mui/material/Autocomplete';
8import Box from '@mui/material/Box';
9import TextField from '@mui/material/TextField';
10import { observer } from 'mobx-react-lite';
11
12import type GraphStore from '../graph/GraphStore';
13import RelationName from '../graph/RelationName';
14
15function SymbolSelector({ graph }: { graph: GraphStore }): JSX.Element {
16 const {
17 selectedSymbol,
18 semantics: { relations },
19 } = graph;
20
21 return (
22 <Autocomplete
23 renderInput={(params) => (
24 <TextField
25 {...{
26 ...params,
27 InputLabelProps: {
28 ...params.InputLabelProps,
29 // Workaround for type errors.
30 className: params.InputLabelProps.className ?? '',
31 style: params.InputLabelProps.style ?? {},
32 },
33 }}
34 variant="standard"
35 size="medium"
36 placeholder="Symbol"
37 />
38 )}
39 options={relations}
40 getOptionLabel={(option) => option.name}
41 renderOption={(props, option) => (
42 <Box component="li" {...props}>
43 <RelationName metadata={option} />
44 </Box>
45 )}
46 value={selectedSymbol ?? null}
47 isOptionEqualToValue={(option, value) => option.name === value.name}
48 onChange={(_event, value) => graph.setSelectedSymbol(value ?? undefined)}
49 sx={(theme) => ({
50 flexBasis: 200,
51 maxWidth: 600,
52 flexGrow: 1,
53 flexShrink: 1,
54 '.MuiInput-underline::before': {
55 borderColor:
56 theme.palette.mode === 'dark'
57 ? theme.palette.divider
58 : theme.palette.outer.border,
59 },
60 })}
61 />
62 );
63}
64
65export default observer(SymbolSelector);
diff --git a/subprojects/frontend/src/table/TableArea.tsx b/subprojects/frontend/src/table/TableArea.tsx
new file mode 100644
index 00000000..cf37b96a
--- /dev/null
+++ b/subprojects/frontend/src/table/TableArea.tsx
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { observer } from 'mobx-react-lite';
8
9import Loading from '../Loading';
10import { useRootStore } from '../RootStoreProvider';
11
12import RelationGrid from './RelationGrid';
13
14function TablePane(): JSX.Element {
15 const { editorStore } = useRootStore();
16
17 if (editorStore === undefined) {
18 return <Loading />;
19 }
20
21 return <RelationGrid graph={editorStore.graph} />;
22}
23
24export default observer(TablePane);
diff --git a/subprojects/frontend/src/table/TablePane.tsx b/subprojects/frontend/src/table/TablePane.tsx
new file mode 100644
index 00000000..01442c3a
--- /dev/null
+++ b/subprojects/frontend/src/table/TablePane.tsx
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Stack from '@mui/material/Stack';
8import { Suspense, lazy } from 'react';
9
10import Loading from '../Loading';
11
12const TableArea = lazy(() => import('./TableArea'));
13
14export default function TablePane(): JSX.Element {
15 return (
16 <Stack direction="column" height="100%" overflow="auto" alignItems="center">
17 <Suspense fallback={<Loading />}>
18 <TableArea />
19 </Suspense>
20 </Stack>
21 );
22}
diff --git a/subprojects/frontend/src/table/TableToolbar.tsx b/subprojects/frontend/src/table/TableToolbar.tsx
new file mode 100644
index 00000000..b14e73c5
--- /dev/null
+++ b/subprojects/frontend/src/table/TableToolbar.tsx
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import Stack from '@mui/material/Stack';
8import {
9 GridToolbarColumnsButton,
10 GridToolbarContainer,
11 GridToolbarExport,
12 GridToolbarFilterButton,
13} from '@mui/x-data-grid';
14
15import type GraphStore from '../graph/GraphStore';
16
17import SymbolSelector from './SymbolSelector';
18
19export default function TableToolbar({
20 graph,
21}: {
22 graph: GraphStore;
23}): JSX.Element {
24 return (
25 <GridToolbarContainer
26 sx={{
27 display: 'flex',
28 flexDirection: 'row',
29 flexWrap: 'wrap-reverse',
30 justifyContent: 'space-between',
31 }}
32 >
33 <Stack direction="row" flexWrap="wrap">
34 <GridToolbarColumnsButton />
35 <GridToolbarFilterButton />
36 <GridToolbarExport />
37 </Stack>
38 <SymbolSelector graph={graph} />
39 </GridToolbarContainer>
40 );
41}
diff --git a/subprojects/frontend/src/table/ValueRenderer.tsx b/subprojects/frontend/src/table/ValueRenderer.tsx
new file mode 100644
index 00000000..ac5700e4
--- /dev/null
+++ b/subprojects/frontend/src/table/ValueRenderer.tsx
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import CancelIcon from '@mui/icons-material/Cancel';
8import LabelIcon from '@mui/icons-material/Label';
9import LabelOutlinedIcon from '@mui/icons-material/LabelOutlined';
10import { styled } from '@mui/material/styles';
11
12const Label = styled('div', {
13 name: 'ValueRenderer-Label',
14 shouldForwardProp: (prop) => prop !== 'value',
15})<{
16 value: 'TRUE' | 'UNKNOWN' | 'ERROR';
17}>(({ theme, value }) => ({
18 display: 'flex',
19 alignItems: 'center',
20 ...(value === 'UNKNOWN'
21 ? {
22 color: theme.palette.text.secondary,
23 }
24 : {}),
25 ...(value === 'ERROR'
26 ? {
27 color: theme.palette.error.main,
28 }
29 : {}),
30 '& svg': {
31 marginRight: theme.spacing(0.5),
32 },
33}));
34
35export default function ValueRenderer({
36 value,
37}: {
38 value: string | undefined;
39}): React.ReactNode {
40 switch (value) {
41 case 'TRUE':
42 return (
43 <Label value={value}>
44 <LabelIcon fontSize="small" /> true
45 </Label>
46 );
47 case 'UNKNOWN':
48 return (
49 <Label value={value}>
50 <LabelOutlinedIcon fontSize="small" /> unknown
51 </Label>
52 );
53 case 'ERROR':
54 return (
55 <Label value={value}>
56 <CancelIcon fontSize="small" /> error
57 </Label>
58 );
59 default:
60 return value;
61 }
62}
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx
index 78146f25..18310147 100644
--- a/subprojects/frontend/src/theme/ThemeProvider.tsx
+++ b/subprojects/frontend/src/theme/ThemeProvider.tsx
@@ -75,13 +75,15 @@ function createResponsiveTheme(
75 ...options, 75 ...options,
76 typography: { 76 typography: {
77 fontFamily: 77 fontFamily:
78 '"Inter Variable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', 78 '"Open Sans Variable", "Open Sans", "Roboto", "Helvetica", "Arial", sans-serif',
79 fontWeightMedium: 600, 79 fontWeightMedium: 500,
80 fontWeightEditorNormal: 400, 80 fontWeightEditorNormal: 400,
81 fontWeightEditorBold: 700, 81 fontWeightEditorBold: 700,
82 button: { 82 button: {
83 // 24px line height for 14px button text to fix browser rounding errors. 83 fontWeight: 600,
84 lineHeight: 1.714286, 84 fontVariationSettings: '"wdth" 87.5',
85 fontSize: '1rem',
86 lineHeight: 1.5,
85 }, 87 },
86 editor: { 88 editor: {
87 fontFamily: 89 fontFamily:
@@ -151,7 +153,7 @@ function createResponsiveTheme(
151 }, {}), 153 }, {}),
152 }, 154 },
153 }, 155 },
154 sizeSmall: { fontSize: '0.75rem' }, 156 sizeSmall: { fontSize: '0.875rem', lineHeight: '1.75' },
155 sizeLarge: { fontSize: '1rem' }, 157 sizeLarge: { fontSize: '1rem' },
156 text: { '&.rounded': { padding: '6px 14px' } }, 158 text: { '&.rounded': { padding: '6px 14px' } },
157 textSizeSmall: { '&.rounded': { padding: '4px 8px' } }, 159 textSizeSmall: { '&.rounded': { padding: '4px 8px' } },
@@ -287,7 +289,7 @@ const darkTheme = (() => {
287 secondary: secondaryText, 289 secondary: secondaryText,
288 disabled: '#5c6370', 290 disabled: '#5c6370',
289 }, 291 },
290 divider: alpha(secondaryText, 0.24), 292 divider: alpha(primaryText, 0.24),
291 outer: { 293 outer: {
292 background: darkBackground, 294 background: darkBackground,
293 border: '#181a1f', 295 border: '#181a1f',
diff --git a/subprojects/frontend/src/theme/ThemeStore.ts b/subprojects/frontend/src/theme/ThemeStore.ts
index 7c657449..12449b94 100644
--- a/subprojects/frontend/src/theme/ThemeStore.ts
+++ b/subprojects/frontend/src/theme/ThemeStore.ts
@@ -12,11 +12,19 @@ export enum ThemePreference {
12 PreferDark, 12 PreferDark,
13} 13}
14 14
15export type SelectedPane = 'code' | 'graph' | 'table';
16
15export default class ThemeStore { 17export default class ThemeStore {
16 preference = ThemePreference.System; 18 preference = ThemePreference.System;
17 19
18 systemDarkMode: boolean; 20 systemDarkMode: boolean;
19 21
22 showCode = true;
23
24 showGraph = true;
25
26 showTable = false;
27
20 constructor() { 28 constructor() {
21 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 29 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
22 this.systemDarkMode = mediaQuery.matches; 30 this.systemDarkMode = mediaQuery.matches;
@@ -48,4 +56,44 @@ export default class ThemeStore {
48 : ThemePreference.PreferDark; 56 : ThemePreference.PreferDark;
49 } 57 }
50 } 58 }
59
60 toggleCode(): void {
61 if (!this.showGraph && !this.showTable) {
62 return;
63 }
64 this.showCode = !this.showCode;
65 }
66
67 toggleGraph(): void {
68 if (!this.showCode && !this.showTable) {
69 return;
70 }
71 this.showGraph = !this.showGraph;
72 }
73
74 toggleTable(): void {
75 if (!this.showCode && !this.showGraph) {
76 return;
77 }
78 this.showTable = !this.showTable;
79 }
80
81 get selectedPane(): SelectedPane {
82 if (this.showCode) {
83 return 'code';
84 }
85 if (this.showGraph) {
86 return 'graph';
87 }
88 if (this.showTable) {
89 return 'table';
90 }
91 return 'code';
92 }
93
94 setSelectedPane(pane: SelectedPane, keepCode = true): void {
95 this.showCode = pane === 'code' || (keepCode && this.showCode);
96 this.showGraph = pane === 'graph';
97 this.showTable = pane === 'table';
98 }
51} 99}
diff --git a/subprojects/frontend/src/utils/svgURL.ts b/subprojects/frontend/src/utils/svgURL.ts
new file mode 100644
index 00000000..9b8ecbd5
--- /dev/null
+++ b/subprojects/frontend/src/utils/svgURL.ts
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7export default function svgURL(svg: string): string {
8 return `url('data:image/svg+xml;utf8,${svg}')`;
9}
diff --git a/subprojects/frontend/src/xtext/BackendConfig.ts b/subprojects/frontend/src/xtext/BackendConfig.ts
index 4c7eac5f..e7043bd5 100644
--- a/subprojects/frontend/src/xtext/BackendConfig.ts
+++ b/subprojects/frontend/src/xtext/BackendConfig.ts
@@ -11,7 +11,7 @@ import { z } from 'zod';
11export const ENDPOINT = 'config.json'; 11export const ENDPOINT = 'config.json';
12 12
13const BackendConfig = z.object({ 13const BackendConfig = z.object({
14 webSocketURL: z.string().url(), 14 webSocketURL: z.string().url().optional(),
15}); 15});
16 16
17type BackendConfig = z.infer<typeof BackendConfig>; 17type BackendConfig = z.infer<typeof BackendConfig>;
diff --git a/subprojects/frontend/src/xtext/ContentAssistService.ts b/subprojects/frontend/src/xtext/ContentAssistService.ts
index fd30c4f9..ac8ab36a 100644
--- a/subprojects/frontend/src/xtext/ContentAssistService.ts
+++ b/subprojects/frontend/src/xtext/ContentAssistService.ts
@@ -248,10 +248,20 @@ export default class ContentAssistService {
248 if (lastTo === undefined) { 248 if (lastTo === undefined) {
249 return true; 249 return true;
250 } 250 }
251 const [transformedFrom, transformedTo] = this.mapRangeInclusive( 251 let transformedFrom: number;
252 lastFrom, 252 let transformedTo: number;
253 lastTo, 253 try {
254 ); 254 [transformedFrom, transformedTo] = this.mapRangeInclusive(
255 lastFrom,
256 lastTo,
257 );
258 } catch (error) {
259 if (error instanceof RangeError) {
260 log.debug('Invalidating cache due to invalid range', error);
261 return true;
262 }
263 throw error;
264 }
255 let invalidate = false; 265 let invalidate = false;
256 transaction.changes.iterChangedRanges((fromA, toA) => { 266 transaction.changes.iterChangedRanges((fromA, toA) => {
257 if (fromA < transformedFrom || toA > transformedTo) { 267 if (fromA < transformedFrom || toA > transformedTo) {
diff --git a/subprojects/frontend/src/xtext/SemanticsService.ts b/subprojects/frontend/src/xtext/SemanticsService.ts
new file mode 100644
index 00000000..d68b87a9
--- /dev/null
+++ b/subprojects/frontend/src/xtext/SemanticsService.ts
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import type EditorStore from '../editor/EditorStore';
8
9import type ValidationService from './ValidationService';
10import { SemanticsResult } from './xtextServiceResults';
11
12export default class SemanticsService {
13 constructor(
14 private readonly store: EditorStore,
15 private readonly validationService: ValidationService,
16 ) {}
17
18 onPush(push: unknown): void {
19 const result = SemanticsResult.parse(push);
20 if ('issues' in result) {
21 this.validationService.setSemanticsIssues(result.issues);
22 } else {
23 this.validationService.setSemanticsIssues([]);
24 if ('error' in result) {
25 this.store.setSemanticsError(result.error);
26 } else {
27 this.store.setSemantics(result);
28 }
29 }
30 this.store.analysisCompleted();
31 }
32}
diff --git a/subprojects/frontend/src/xtext/UpdateService.ts b/subprojects/frontend/src/xtext/UpdateService.ts
index ee5ebde2..1ac722e1 100644
--- a/subprojects/frontend/src/xtext/UpdateService.ts
+++ b/subprojects/frontend/src/xtext/UpdateService.ts
@@ -133,6 +133,7 @@ export default class UpdateService {
133 return; 133 return;
134 } 134 }
135 log.trace('Editor delta', delta); 135 log.trace('Editor delta', delta);
136 this.store.analysisStarted();
136 const result = await this.webSocketClient.send({ 137 const result = await this.webSocketClient.send({
137 resource: this.resourceName, 138 resource: this.resourceName,
138 serviceType: 'update', 139 serviceType: 'update',
@@ -157,6 +158,7 @@ export default class UpdateService {
157 private async updateFullTextExclusive(): Promise<void> { 158 private async updateFullTextExclusive(): Promise<void> {
158 log.debug('Performing full text update'); 159 log.debug('Performing full text update');
159 this.tracker.prepareFullTextUpdateExclusive(); 160 this.tracker.prepareFullTextUpdateExclusive();
161 this.store.analysisStarted();
160 const result = await this.webSocketClient.send({ 162 const result = await this.webSocketClient.send({
161 resource: this.resourceName, 163 resource: this.resourceName,
162 serviceType: 'update', 164 serviceType: 'update',
diff --git a/subprojects/frontend/src/xtext/ValidationService.ts b/subprojects/frontend/src/xtext/ValidationService.ts
index 64fb63eb..1a896db3 100644
--- a/subprojects/frontend/src/xtext/ValidationService.ts
+++ b/subprojects/frontend/src/xtext/ValidationService.ts
@@ -9,7 +9,7 @@ import type { Diagnostic } from '@codemirror/lint';
9import type EditorStore from '../editor/EditorStore'; 9import type EditorStore from '../editor/EditorStore';
10 10
11import type UpdateService from './UpdateService'; 11import type UpdateService from './UpdateService';
12import { ValidationResult } from './xtextServiceResults'; 12import { Issue, ValidationResult } from './xtextServiceResults';
13 13
14export default class ValidationService { 14export default class ValidationService {
15 constructor( 15 constructor(
@@ -17,11 +17,41 @@ export default class ValidationService {
17 private readonly updateService: UpdateService, 17 private readonly updateService: UpdateService,
18 ) {} 18 ) {}
19 19
20 private lastValidationIssues: Issue[] = [];
21
22 private lastSemanticsIssues: Issue[] = [];
23
20 onPush(push: unknown): void { 24 onPush(push: unknown): void {
21 const { issues } = ValidationResult.parse(push); 25 ({ issues: this.lastValidationIssues } = ValidationResult.parse(push));
26 this.lastSemanticsIssues = [];
27 this.updateDiagnostics();
28 if (
29 this.lastValidationIssues.some(({ severity }) => severity === 'error')
30 ) {
31 this.store.analysisCompleted(true);
32 }
33 }
34
35 onDisconnect(): void {
36 this.store.updateDiagnostics([]);
37 this.lastValidationIssues = [];
38 this.lastSemanticsIssues = [];
39 }
40
41 setSemanticsIssues(issues: Issue[]): void {
42 this.lastSemanticsIssues = issues;
43 this.updateDiagnostics();
44 }
45
46 private updateDiagnostics(): void {
22 const allChanges = this.updateService.computeChangesSinceLastUpdate(); 47 const allChanges = this.updateService.computeChangesSinceLastUpdate();
23 const diagnostics: Diagnostic[] = []; 48 const diagnostics: Diagnostic[] = [];
24 issues.forEach(({ offset, length, severity, description }) => { 49 function createDiagnostic({
50 offset,
51 length,
52 severity,
53 description,
54 }: Issue): void {
25 if (severity === 'ignore') { 55 if (severity === 'ignore') {
26 return; 56 return;
27 } 57 }
@@ -31,11 +61,9 @@ export default class ValidationService {
31 severity, 61 severity,
32 message: description, 62 message: description,
33 }); 63 });
34 }); 64 }
65 this.lastValidationIssues.forEach(createDiagnostic);
66 this.lastSemanticsIssues.forEach(createDiagnostic);
35 this.store.updateDiagnostics(diagnostics); 67 this.store.updateDiagnostics(diagnostics);
36 } 68 }
37
38 onDisconnect(): void {
39 this.store.updateDiagnostics([]);
40 }
41} 69}
diff --git a/subprojects/frontend/src/xtext/XtextClient.ts b/subprojects/frontend/src/xtext/XtextClient.ts
index e8181af0..87778084 100644
--- a/subprojects/frontend/src/xtext/XtextClient.ts
+++ b/subprojects/frontend/src/xtext/XtextClient.ts
@@ -17,6 +17,7 @@ import getLogger from '../utils/getLogger';
17import ContentAssistService from './ContentAssistService'; 17import ContentAssistService from './ContentAssistService';
18import HighlightingService from './HighlightingService'; 18import HighlightingService from './HighlightingService';
19import OccurrencesService from './OccurrencesService'; 19import OccurrencesService from './OccurrencesService';
20import SemanticsService from './SemanticsService';
20import UpdateService from './UpdateService'; 21import UpdateService from './UpdateService';
21import ValidationService from './ValidationService'; 22import ValidationService from './ValidationService';
22import XtextWebSocketClient from './XtextWebSocketClient'; 23import XtextWebSocketClient from './XtextWebSocketClient';
@@ -37,7 +38,12 @@ export default class XtextClient {
37 38
38 private readonly occurrencesService: OccurrencesService; 39 private readonly occurrencesService: OccurrencesService;
39 40
40 constructor(store: EditorStore, private readonly pwaStore: PWAStore) { 41 private readonly semanticsService: SemanticsService;
42
43 constructor(
44 private readonly store: EditorStore,
45 private readonly pwaStore: PWAStore,
46 ) {
41 this.webSocketClient = new XtextWebSocketClient( 47 this.webSocketClient = new XtextWebSocketClient(
42 () => this.onReconnect(), 48 () => this.onReconnect(),
43 () => this.onDisconnect(), 49 () => this.onDisconnect(),
@@ -51,6 +57,7 @@ export default class XtextClient {
51 ); 57 );
52 this.validationService = new ValidationService(store, this.updateService); 58 this.validationService = new ValidationService(store, this.updateService);
53 this.occurrencesService = new OccurrencesService(store, this.updateService); 59 this.occurrencesService = new OccurrencesService(store, this.updateService);
60 this.semanticsService = new SemanticsService(store, this.validationService);
54 } 61 }
55 62
56 start(): void { 63 start(): void {
@@ -64,6 +71,7 @@ export default class XtextClient {
64 } 71 }
65 72
66 private onDisconnect(): void { 73 private onDisconnect(): void {
74 this.store.analysisCompleted(true);
67 this.highlightingService.onDisconnect(); 75 this.highlightingService.onDisconnect();
68 this.validationService.onDisconnect(); 76 this.validationService.onDisconnect();
69 this.occurrencesService.onDisconnect(); 77 this.occurrencesService.onDisconnect();
@@ -111,6 +119,9 @@ export default class XtextClient {
111 case 'validate': 119 case 'validate':
112 this.validationService.onPush(push); 120 this.validationService.onPush(push);
113 return; 121 return;
122 case 'semantics':
123 this.semanticsService.onPush(push);
124 return;
114 default: 125 default:
115 throw new Error('Unknown service'); 126 throw new Error('Unknown service');
116 } 127 }
diff --git a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
index 6bb7eec8..963c1d4c 100644
--- a/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
+++ b/subprojects/frontend/src/xtext/XtextWebSocketClient.ts
@@ -282,7 +282,10 @@ export default class XtextWebSocketClient {
282 log.debug('Creating WebSocket'); 282 log.debug('Creating WebSocket');
283 283
284 (async () => { 284 (async () => {
285 const { webSocketURL } = await fetchBackendConfig(); 285 let { webSocketURL } = await fetchBackendConfig();
286 if (webSocketURL === undefined) {
287 webSocketURL = `${window.origin.replace(/^http/, 'ws')}/xtext-service`;
288 }
286 this.openWebSocketWithURL(webSocketURL); 289 this.openWebSocketWithURL(webSocketURL);
287 })().catch((error) => { 290 })().catch((error) => {
288 log.error('Error while initializing connection', error); 291 log.error('Error while initializing connection', error);
diff --git a/subprojects/frontend/src/xtext/xtextMessages.ts b/subprojects/frontend/src/xtext/xtextMessages.ts
index bbbff064..971720e1 100644
--- a/subprojects/frontend/src/xtext/xtextMessages.ts
+++ b/subprojects/frontend/src/xtext/xtextMessages.ts
@@ -34,7 +34,11 @@ export const XtextWebErrorResponse = z.object({
34 34
35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>; 35export type XtextWebErrorResponse = z.infer<typeof XtextWebErrorResponse>;
36 36
37export const XtextWebPushService = z.enum(['highlight', 'validate']); 37export const XtextWebPushService = z.enum([
38 'highlight',
39 'validate',
40 'semantics',
41]);
38 42
39export type XtextWebPushService = z.infer<typeof XtextWebPushService>; 43export type XtextWebPushService = z.infer<typeof XtextWebPushService>;
40 44
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index d3b467ad..caf2cf0b 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -125,3 +125,49 @@ export const FormattingResult = DocumentStateResult.extend({
125}); 125});
126 126
127export type FormattingResult = z.infer<typeof FormattingResult>; 127export type FormattingResult = z.infer<typeof FormattingResult>;
128
129export const NodeMetadata = z.object({
130 name: z.string(),
131 simpleName: z.string(),
132 kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']),
133});
134
135export type NodeMetadata = z.infer<typeof NodeMetadata>;
136
137export const RelationMetadata = z.object({
138 name: z.string(),
139 simpleName: z.string(),
140 arity: z.number().nonnegative(),
141 detail: z.union([
142 z.object({ type: z.literal('class'), abstractClass: z.boolean() }),
143 z.object({ type: z.literal('reference'), containment: z.boolean() }),
144 z.object({
145 type: z.literal('opposite'),
146 container: z.boolean(),
147 opposite: z.string(),
148 }),
149 z.object({ type: z.literal('predicate'), error: z.boolean() }),
150 z.object({ type: z.literal('builtin') }),
151 ]),
152});
153
154export type RelationMetadata = z.infer<typeof RelationMetadata>;
155
156export const SemanticsSuccessResult = z.object({
157 nodes: NodeMetadata.array(),
158 relations: RelationMetadata.array(),
159 partialInterpretation: z.record(
160 z.string(),
161 z.union([z.number(), z.string()]).array().array(),
162 ),
163});
164
165export type SemanticsSuccessResult = z.infer<typeof SemanticsSuccessResult>;
166
167export const SemanticsResult = z.union([
168 z.object({ error: z.string() }),
169 z.object({ issues: Issue.array() }),
170 SemanticsSuccessResult,
171]);
172
173export type SemanticsResult = z.infer<typeof SemanticsResult>;
diff --git a/subprojects/frontend/tsconfig.base.json b/subprojects/frontend/tsconfig.base.json
index 5ef50b5e..545eca35 100644
--- a/subprojects/frontend/tsconfig.base.json
+++ b/subprojects/frontend/tsconfig.base.json
@@ -2,7 +2,7 @@
2 * Copyright (c) Microsoft Corporation. 2 * Copyright (c) Microsoft Corporation.
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 * 6 *
7 * This file is based on 7 * This file is based on
8 * https://github.com/tsconfig/bases/blob/7db25a41bc5a9c0f66d91f6f3aa28438afcb2f18/bases/strictest.json 8 * https://github.com/tsconfig/bases/blob/7db25a41bc5a9c0f66d91f6f3aa28438afcb2f18/bases/strictest.json
diff --git a/subprojects/frontend/types/ImportMeta.d.ts b/subprojects/frontend/types/ImportMeta.d.ts
index f5a32ef1..096f088b 100644
--- a/subprojects/frontend/types/ImportMeta.d.ts
+++ b/subprojects/frontend/types/ImportMeta.d.ts
@@ -2,7 +2,7 @@
2 * Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors 2 * Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
3 * Copyright (c) 2021-2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (c) 2021-2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 */ 6 */
7 7
8interface ImportMeta { 8interface ImportMeta {
diff --git a/subprojects/frontend/types/grammar.d.ts b/subprojects/frontend/types/grammar.d.ts
index e7a7eebf..92f99ec3 100644
--- a/subprojects/frontend/types/grammar.d.ts
+++ b/subprojects/frontend/types/grammar.d.ts
@@ -2,7 +2,7 @@
2 * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others 2 * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 */ 6 */
7 7
8declare module '*.grammar' { 8declare module '*.grammar' {
diff --git a/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts b/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts
index 4ef9f4e3..ce89a44c 100644
--- a/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts
+++ b/subprojects/frontend/types/node/@lezer-generator-rollup.d.ts
@@ -2,7 +2,7 @@
2 * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others 2 * Copyright (C) 2018 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (C) 2021-2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 */ 6 */
7 7
8// We have to explicitly redeclare the type of the `./rollup` ESM export of `@lezer/generator`, 8// We have to explicitly redeclare the type of the `./rollup` ESM export of `@lezer/generator`,
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts
index 9e08ccc4..63d5245f 100644
--- a/subprojects/frontend/vite.config.ts
+++ b/subprojects/frontend/vite.config.ts
@@ -17,6 +17,7 @@ import detectDevModeOptions, {
17 API_ENDPOINT, 17 API_ENDPOINT,
18} from './config/detectDevModeOptions'; 18} from './config/detectDevModeOptions';
19import fetchPackageMetadata from './config/fetchPackageMetadata'; 19import fetchPackageMetadata from './config/fetchPackageMetadata';
20import graphvizUMDVitePlugin from './config/graphvizUMDVitePlugin';
20import manifest from './config/manifest'; 21import manifest from './config/manifest';
21import minifyHTMLVitePlugin from './config/minifyHTMLVitePlugin'; 22import minifyHTMLVitePlugin from './config/minifyHTMLVitePlugin';
22import preloadFontsVitePlugin from './config/preloadFontsVitePlugin'; 23import preloadFontsVitePlugin from './config/preloadFontsVitePlugin';
@@ -29,8 +30,8 @@ const { mode, isDevelopment, devModePlugins, serverOptions } =
29process.env['NODE_ENV'] ??= mode; 30process.env['NODE_ENV'] ??= mode;
30 31
31const fontsGlob = [ 32const fontsGlob = [
32 'inter-latin-variable-wghtOnly-normal-*.woff2', 33 'open-sans-latin-wdth-{normal,italic}-*.woff2',
33 'jetbrains-mono-latin-variable-wghtOnly-{normal,italic}-*.woff2', 34 'jetbrains-mono-latin-wght-{normal,italic}-*.woff2',
34]; 35];
35 36
36const viteConfig: ViteConfig = { 37const viteConfig: ViteConfig = {
@@ -43,6 +44,7 @@ const viteConfig: ViteConfig = {
43 lezer(), 44 lezer(),
44 preloadFontsVitePlugin(fontsGlob), 45 preloadFontsVitePlugin(fontsGlob),
45 minifyHTMLVitePlugin(), 46 minifyHTMLVitePlugin(),
47 graphvizUMDVitePlugin(),
46 VitePWA({ 48 VitePWA({
47 strategies: 'generateSW', 49 strategies: 'generateSW',
48 registerType: 'prompt', 50 registerType: 'prompt',
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java
index e194ee31..ea90a82e 100644
--- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java
+++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/contentassist/ProblemCrossrefProposalProvider.java
@@ -5,39 +5,60 @@
5 */ 5 */
6package tools.refinery.language.ide.contentassist; 6package tools.refinery.language.ide.contentassist;
7 7
8import java.util.Objects; 8import com.google.inject.Inject;
9
10import org.eclipse.emf.ecore.EObject; 9import org.eclipse.emf.ecore.EObject;
11import org.eclipse.emf.ecore.util.EcoreUtil; 10import org.eclipse.emf.ecore.util.EcoreUtil;
12import org.eclipse.xtext.CrossReference; 11import org.eclipse.xtext.CrossReference;
13import org.eclipse.xtext.GrammarUtil; 12import org.eclipse.xtext.GrammarUtil;
14import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext; 13import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext;
15import org.eclipse.xtext.ide.editor.contentassist.ContentAssistEntry;
16import org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider; 14import org.eclipse.xtext.ide.editor.contentassist.IdeCrossrefProposalProvider;
15import org.eclipse.xtext.naming.QualifiedName;
17import org.eclipse.xtext.nodemodel.util.NodeModelUtils; 16import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
18import org.eclipse.xtext.resource.IEObjectDescription; 17import org.eclipse.xtext.resource.IEObjectDescription;
19 18import org.eclipse.xtext.scoping.IScope;
20import com.google.inject.Inject;
21
22import tools.refinery.language.model.problem.Problem; 19import tools.refinery.language.model.problem.Problem;
20import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
23import tools.refinery.language.resource.ReferenceCounter; 21import tools.refinery.language.resource.ReferenceCounter;
24import tools.refinery.language.utils.ProblemUtil; 22import tools.refinery.language.utils.ProblemUtil;
25 23
24import java.util.ArrayList;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Objects;
28
26public class ProblemCrossrefProposalProvider extends IdeCrossrefProposalProvider { 29public class ProblemCrossrefProposalProvider extends IdeCrossrefProposalProvider {
27 @Inject 30 @Inject
28 private ReferenceCounter referenceCounter; 31 private ReferenceCounter referenceCounter;
29 32
30 @Override 33 @Override
31 protected ContentAssistEntry createProposal(IEObjectDescription candidate, CrossReference crossRef, 34 protected Iterable<IEObjectDescription> queryScope(IScope scope, CrossReference crossReference,
32 ContentAssistContext context) { 35 ContentAssistContext context) {
33 if (!shouldCreateProposal(candidate, crossRef, context)) { 36 var eObjectDescriptionsByName = new HashMap<QualifiedName, List<IEObjectDescription>>();
34 return null; 37 for (var candidate : super.queryScope(scope, crossReference, context)) {
38 if (isExistingObject(candidate, crossReference, context)) {
39 // {@code getQualifiedName()} will refer to the full name for objects that are loaded from the global
40 // scope, but {@code getName()} returns the qualified name that we set in
41 // {@code ProblemResourceDescriptionStrategy}.
42 var qualifiedName = candidate.getName();
43 var candidateList = eObjectDescriptionsByName.computeIfAbsent(qualifiedName,
44 ignored -> new ArrayList<>());
45 candidateList.add(candidate);
46 }
35 } 47 }
36 return super.createProposal(candidate, crossRef, context); 48 var eObjectDescriptions = new ArrayList<IEObjectDescription>();
49 for (var candidates : eObjectDescriptionsByName.values()) {
50 if (candidates.size() == 1) {
51 var candidate = candidates.get(0);
52 if (shouldBeVisible(candidate)) {
53 eObjectDescriptions.add(candidate);
54 }
55 }
56 }
57 return eObjectDescriptions;
37 } 58 }
38 59
39 protected boolean shouldCreateProposal(IEObjectDescription candidate, CrossReference crossRef, 60 protected boolean isExistingObject(IEObjectDescription candidate, CrossReference crossRef,
40 ContentAssistContext context) { 61 ContentAssistContext context) {
41 var rootModel = context.getRootModel(); 62 var rootModel = context.getRootModel();
42 var eObjectOrProxy = candidate.getEObjectOrProxy(); 63 var eObjectOrProxy = candidate.getEObjectOrProxy();
43 if (!Objects.equals(rootModel.eResource(), eObjectOrProxy.eResource())) { 64 if (!Objects.equals(rootModel.eResource(), eObjectOrProxy.eResource())) {
@@ -60,6 +81,11 @@ public class ProblemCrossrefProposalProvider extends IdeCrossrefProposalProvider
60 return true; 81 return true;
61 } 82 }
62 83
84 protected boolean shouldBeVisible(IEObjectDescription candidate) {
85 var errorPredicate = candidate.getUserData(ProblemResourceDescriptionStrategy.ERROR_PREDICATE);
86 return !ProblemResourceDescriptionStrategy.ERROR_PREDICATE_TRUE.equals(errorPredicate);
87 }
88
63 protected EObject getCurrentValue(CrossReference crossRef, ContentAssistContext context) { 89 protected EObject getCurrentValue(CrossReference crossRef, ContentAssistContext context) {
64 var value = getCurrentValue(crossRef, context.getCurrentModel()); 90 var value = getCurrentValue(crossRef, context.getCurrentModel());
65 if (value != null) { 91 if (value != null) {
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
index e8f97d51..ae8c70e0 100644
--- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
+++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
@@ -16,6 +16,7 @@ import org.eclipse.xtext.nodemodel.INode;
16import org.eclipse.xtext.nodemodel.util.NodeModelUtils; 16import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
17import org.eclipse.xtext.service.OperationCanceledManager; 17import org.eclipse.xtext.service.OperationCanceledManager;
18import org.eclipse.xtext.util.CancelIndicator; 18import org.eclipse.xtext.util.CancelIndicator;
19import org.jetbrains.annotations.NotNull;
19import tools.refinery.language.model.problem.*; 20import tools.refinery.language.model.problem.*;
20import tools.refinery.language.utils.ProblemDesugarer; 21import tools.refinery.language.utils.ProblemDesugarer;
21import tools.refinery.language.utils.ProblemUtil; 22import tools.refinery.language.utils.ProblemUtil;
@@ -94,9 +95,16 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
94 } 95 }
95 96
96 protected String[] getHighlightClass(EObject eObject, EReference reference) { 97 protected String[] getHighlightClass(EObject eObject, EReference reference) {
98 boolean isError = ProblemUtil.isError(eObject);
97 if (ProblemUtil.isBuiltIn(eObject)) { 99 if (ProblemUtil.isBuiltIn(eObject)) {
98 return new String[] { BUILTIN_CLASS }; 100 var className = isError ? ERROR_CLASS : BUILTIN_CLASS;
101 return new String[] { className };
99 } 102 }
103 return getUserDefinedElementHighlightClass(eObject, reference, isError);
104 }
105
106 @NotNull
107 private String[] getUserDefinedElementHighlightClass(EObject eObject, EReference reference, boolean isError) {
100 ImmutableList.Builder<String> classesBuilder = ImmutableList.builder(); 108 ImmutableList.Builder<String> classesBuilder = ImmutableList.builder();
101 if (eObject instanceof ClassDeclaration classDeclaration && classDeclaration.isAbstract()) { 109 if (eObject instanceof ClassDeclaration classDeclaration && classDeclaration.isAbstract()) {
102 classesBuilder.add(ABSTRACT_CLASS); 110 classesBuilder.add(ABSTRACT_CLASS);
@@ -105,8 +113,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
105 && desugarer.isContainmentReference(referenceDeclaration)) { 113 && desugarer.isContainmentReference(referenceDeclaration)) {
106 classesBuilder.add(CONTAINMENT_CLASS); 114 classesBuilder.add(CONTAINMENT_CLASS);
107 } 115 }
108 if (eObject instanceof PredicateDefinition predicateDefinition 116 if (isError) {
109 && predicateDefinition.getKind() == PredicateKind.ERROR) {
110 classesBuilder.add(ERROR_CLASS); 117 classesBuilder.add(ERROR_CLASS);
111 } 118 }
112 if (eObject instanceof Node node) { 119 if (eObject instanceof Node node) {
diff --git a/subprojects/language-model/problem.aird b/subprojects/language-model/problem.aird
index 286dabd6..a307ae83 100644
--- a/subprojects/language-model/problem.aird
+++ b/subprojects/language-model/problem.aird
@@ -7,7 +7,7 @@
7 <semanticResources>build/resources/main/model/problem.genmodel</semanticResources> 7 <semanticResources>build/resources/main/model/problem.genmodel</semanticResources>
8 <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg"> 8 <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg">
9 <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/> 9 <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/>
10 <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="f104e460-dce9-4947-b526-467cf8618336"> 10 <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="7fa3b02a-a620-4e42-87d5-b2cc35ee8070">
11 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/> 11 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/>
12 <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/> 12 <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/>
13 </ownedRepresentationDescriptors> 13 </ownedRepresentationDescriptors>
@@ -79,9 +79,9 @@
79 <children xmi:type="notation:Node" xmi:id="_fit3kKA5EeuqkpDnuik1sg" type="2003" element="_fihqUKA5EeuqkpDnuik1sg"> 79 <children xmi:type="notation:Node" xmi:id="_fit3kKA5EeuqkpDnuik1sg" type="2003" element="_fihqUKA5EeuqkpDnuik1sg">
80 <children xmi:type="notation:Node" xmi:id="_fit3k6A5EeuqkpDnuik1sg" type="5007"/> 80 <children xmi:type="notation:Node" xmi:id="_fit3k6A5EeuqkpDnuik1sg" type="5007"/>
81 <children xmi:type="notation:Node" xmi:id="_fit3lKA5EeuqkpDnuik1sg" type="7004"> 81 <children xmi:type="notation:Node" xmi:id="_fit3lKA5EeuqkpDnuik1sg" type="7004">
82 <children xmi:type="notation:Node" xmi:id="_id6DcDNoEe2fD4dIhR_vzA" type="3010" element="_ida7QDNoEe2fD4dIhR_vzA"> 82 <children xmi:type="notation:Node" xmi:id="_bmoagDrQEe62Q_vL_UTCsA" type="3010" element="_blvCoDrQEe62Q_vL_UTCsA">
83 <styles xmi:type="notation:FontStyle" xmi:id="_id6DcTNoEe2fD4dIhR_vzA" fontColor="2697711" fontName="Noto Sans" fontHeight="8"/> 83 <styles xmi:type="notation:FontStyle" xmi:id="_bmoagTrQEe62Q_vL_UTCsA" fontColor="2697711" fontName="Noto Sans" fontHeight="8"/>
84 <layoutConstraint xmi:type="notation:Location" xmi:id="_id6DcjNoEe2fD4dIhR_vzA"/> 84 <layoutConstraint xmi:type="notation:Location" xmi:id="_bmoagjrQEe62Q_vL_UTCsA"/>
85 </children> 85 </children>
86 <styles xmi:type="notation:SortingStyle" xmi:id="_fit3laA5EeuqkpDnuik1sg"/> 86 <styles xmi:type="notation:SortingStyle" xmi:id="_fit3laA5EeuqkpDnuik1sg"/>
87 <styles xmi:type="notation:FilteringStyle" xmi:id="_fit3lqA5EeuqkpDnuik1sg"/> 87 <styles xmi:type="notation:FilteringStyle" xmi:id="_fit3lqA5EeuqkpDnuik1sg"/>
@@ -92,10 +92,6 @@
92 <children xmi:type="notation:Node" xmi:id="_QKLK0KA6EeuqkpDnuik1sg" type="2003" element="_QKD2EKA6EeuqkpDnuik1sg"> 92 <children xmi:type="notation:Node" xmi:id="_QKLK0KA6EeuqkpDnuik1sg" type="2003" element="_QKD2EKA6EeuqkpDnuik1sg">
93 <children xmi:type="notation:Node" xmi:id="_QKLK06A6EeuqkpDnuik1sg" type="5007"/> 93 <children xmi:type="notation:Node" xmi:id="_QKLK06A6EeuqkpDnuik1sg" type="5007"/>
94 <children xmi:type="notation:Node" xmi:id="_QKLK1KA6EeuqkpDnuik1sg" type="7004"> 94 <children xmi:type="notation:Node" xmi:id="_QKLK1KA6EeuqkpDnuik1sg" type="7004">
95 <children xmi:type="notation:Node" xmi:id="_ARsFYBjHEe2_erjsEmF9GQ" type="3010" element="_ARaYkBjHEe2_erjsEmF9GQ">
96 <styles xmi:type="notation:FontStyle" xmi:id="_ARsFYRjHEe2_erjsEmF9GQ" fontColor="2697711" fontName="Noto Sans" fontHeight="8"/>
97 <layoutConstraint xmi:type="notation:Location" xmi:id="_ARsFYhjHEe2_erjsEmF9GQ"/>
98 </children>
99 <styles xmi:type="notation:SortingStyle" xmi:id="_QKLK1aA6EeuqkpDnuik1sg"/> 95 <styles xmi:type="notation:SortingStyle" xmi:id="_QKLK1aA6EeuqkpDnuik1sg"/>
100 <styles xmi:type="notation:FilteringStyle" xmi:id="_QKLK1qA6EeuqkpDnuik1sg"/> 96 <styles xmi:type="notation:FilteringStyle" xmi:id="_QKLK1qA6EeuqkpDnuik1sg"/>
101 </children> 97 </children>
@@ -308,31 +304,6 @@
308 <styles xmi:type="notation:ShapeStyle" xmi:id="_xp1icTNlEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/> 304 <styles xmi:type="notation:ShapeStyle" xmi:id="_xp1icTNlEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
309 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_xp1icjNlEe2fD4dIhR_vzA" x="574" y="1280" width="120" height="100"/> 305 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_xp1icjNlEe2fD4dIhR_vzA" x="574" y="1280" width="120" height="100"/>
310 </children> 306 </children>
311 <children xmi:type="notation:Node" xmi:id="_DFHboDNoEe2fD4dIhR_vzA" type="2003" element="_DD1pQDNoEe2fD4dIhR_vzA">
312 <children xmi:type="notation:Node" xmi:id="_DFICsDNoEe2fD4dIhR_vzA" type="5007"/>
313 <children xmi:type="notation:Node" xmi:id="_DFICsTNoEe2fD4dIhR_vzA" type="7004">
314 <children xmi:type="notation:Node" xmi:id="_FDz88DNoEe2fD4dIhR_vzA" type="3010" element="_FDOuIDNoEe2fD4dIhR_vzA">
315 <styles xmi:type="notation:FontStyle" xmi:id="_FDz88TNoEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
316 <layoutConstraint xmi:type="notation:Location" xmi:id="_FDz88jNoEe2fD4dIhR_vzA"/>
317 </children>
318 <children xmi:type="notation:Node" xmi:id="_F3CwsDNoEe2fD4dIhR_vzA" type="3010" element="_F2glMDNoEe2fD4dIhR_vzA">
319 <styles xmi:type="notation:FontStyle" xmi:id="_F3CwsTNoEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
320 <layoutConstraint xmi:type="notation:Location" xmi:id="_F3CwsjNoEe2fD4dIhR_vzA"/>
321 </children>
322 <children xmi:type="notation:Node" xmi:id="_GTcY0DNoEe2fD4dIhR_vzA" type="3010" element="_GS7bcDNoEe2fD4dIhR_vzA">
323 <styles xmi:type="notation:FontStyle" xmi:id="_GTcY0TNoEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
324 <layoutConstraint xmi:type="notation:Location" xmi:id="_GTcY0jNoEe2fD4dIhR_vzA"/>
325 </children>
326 <children xmi:type="notation:Node" xmi:id="_GyKVIDNoEe2fD4dIhR_vzA" type="3010" element="_Gx7EkDNoEe2fD4dIhR_vzA">
327 <styles xmi:type="notation:FontStyle" xmi:id="_GyKVITNoEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
328 <layoutConstraint xmi:type="notation:Location" xmi:id="_GyKVIjNoEe2fD4dIhR_vzA"/>
329 </children>
330 <styles xmi:type="notation:SortingStyle" xmi:id="_DFICsjNoEe2fD4dIhR_vzA"/>
331 <styles xmi:type="notation:FilteringStyle" xmi:id="_DFICszNoEe2fD4dIhR_vzA"/>
332 </children>
333 <styles xmi:type="notation:ShapeStyle" xmi:id="_DFHboTNoEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/>
334 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_DFHbojNoEe2fD4dIhR_vzA" x="1355" y="1280" width="120" height="100"/>
335 </children>
336 <children xmi:type="notation:Node" xmi:id="_782skF9mEe2rXNsIDUvqhw" type="2003" element="_78pRMF9mEe2rXNsIDUvqhw"> 307 <children xmi:type="notation:Node" xmi:id="_782skF9mEe2rXNsIDUvqhw" type="2003" element="_78pRMF9mEe2rXNsIDUvqhw">
337 <children xmi:type="notation:Node" xmi:id="_783ToF9mEe2rXNsIDUvqhw" type="5007"/> 308 <children xmi:type="notation:Node" xmi:id="_783ToF9mEe2rXNsIDUvqhw" type="5007"/>
338 <children xmi:type="notation:Node" xmi:id="_783ToV9mEe2rXNsIDUvqhw" type="7004"> 309 <children xmi:type="notation:Node" xmi:id="_783ToV9mEe2rXNsIDUvqhw" type="7004">
@@ -1184,17 +1155,17 @@
1184 </edges> 1155 </edges>
1185 <edges xmi:type="notation:Edge" xmi:id="_Nr78MGTzEe2qdtyPWAtoxA" type="4001" element="_NrtSzmTzEe2qdtyPWAtoxA" source="_c-HCQKA4EeuqkpDnuik1sg" target="_dzfLYGTvEe2qdtyPWAtoxA"> 1156 <edges xmi:type="notation:Edge" xmi:id="_Nr78MGTzEe2qdtyPWAtoxA" type="4001" element="_NrtSzmTzEe2qdtyPWAtoxA" source="_c-HCQKA4EeuqkpDnuik1sg" target="_dzfLYGTvEe2qdtyPWAtoxA">
1186 <children xmi:type="notation:Node" xmi:id="_Nr78NGTzEe2qdtyPWAtoxA" type="6001"> 1157 <children xmi:type="notation:Node" xmi:id="_Nr78NGTzEe2qdtyPWAtoxA" type="6001">
1187 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78NWTzEe2qdtyPWAtoxA" x="81"/> 1158 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78NWTzEe2qdtyPWAtoxA" x="79"/>
1188 </children> 1159 </children>
1189 <children xmi:type="notation:Node" xmi:id="_Nr78NmTzEe2qdtyPWAtoxA" type="6002"> 1160 <children xmi:type="notation:Node" xmi:id="_Nr78NmTzEe2qdtyPWAtoxA" type="6002">
1190 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78N2TzEe2qdtyPWAtoxA" x="196" y="10"/> 1161 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78N2TzEe2qdtyPWAtoxA" x="193" y="10"/>
1191 </children> 1162 </children>
1192 <children xmi:type="notation:Node" xmi:id="_Nr78OGTzEe2qdtyPWAtoxA" type="6003"> 1163 <children xmi:type="notation:Node" xmi:id="_Nr78OGTzEe2qdtyPWAtoxA" type="6003">
1193 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78OWTzEe2qdtyPWAtoxA" x="20" y="10"/> 1164 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_Nr78OWTzEe2qdtyPWAtoxA" x="19" y="10"/>
1194 </children> 1165 </children>
1195 <styles xmi:type="notation:ConnectorStyle" xmi:id="_Nr78MWTzEe2qdtyPWAtoxA" routing="Rectilinear"/> 1166 <styles xmi:type="notation:ConnectorStyle" xmi:id="_Nr78MWTzEe2qdtyPWAtoxA" routing="Rectilinear"/>
1196 <styles xmi:type="notation:FontStyle" xmi:id="_Nr78MmTzEe2qdtyPWAtoxA" fontColor="7490599" fontName="Noto Sans" fontHeight="8"/> 1167 <styles xmi:type="notation:FontStyle" xmi:id="_Nr78MmTzEe2qdtyPWAtoxA" fontColor="7490599" fontName="Noto Sans" fontHeight="8"/>
1197 <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_Nr78M2TzEe2qdtyPWAtoxA" points="[-60, 24, 24, 526]$[-84, 24, 0, 526]$[-84, -502, 0, 0]"/> 1168 <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_Nr78M2TzEe2qdtyPWAtoxA" points="[-60, 24, 24, 526]$[-80, 24, 4, 526]$[-80, -502, 4, 0]"/>
1198 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Nr78OmTzEe2qdtyPWAtoxA" id="(0.41379310344827586,0.0)"/> 1169 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Nr78OmTzEe2qdtyPWAtoxA" id="(0.41379310344827586,0.0)"/>
1199 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Nr78O2TzEe2qdtyPWAtoxA" id="(0.3305084745762712,1.0)"/> 1170 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_Nr78O2TzEe2qdtyPWAtoxA" id="(0.3305084745762712,1.0)"/>
1200 </edges> 1171 </edges>
@@ -1272,7 +1243,7 @@
1272 </children> 1243 </children>
1273 <styles xmi:type="notation:ConnectorStyle" xmi:id="_WnX2AWg8Ee25oofngfVl_A" routing="Tree"/> 1244 <styles xmi:type="notation:ConnectorStyle" xmi:id="_WnX2AWg8Ee25oofngfVl_A" routing="Tree"/>
1274 <styles xmi:type="notation:FontStyle" xmi:id="_WnX2Amg8Ee25oofngfVl_A" fontName="Noto Sans" fontHeight="8"/> 1245 <styles xmi:type="notation:FontStyle" xmi:id="_WnX2Amg8Ee25oofngfVl_A" fontName="Noto Sans" fontHeight="8"/>
1275 <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_WnX2A2g8Ee25oofngfVl_A" points="[0, -56, 86, 383]$[0, -320, 86, 119]$[-85, -320, 1, 119]$[-85, -390, 1, 49]"/> 1246 <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_WnX2A2g8Ee25oofngfVl_A" points="[0, -56, 44, 383]$[0, -316, 44, 123]$[-43, -316, 1, 123]$[-43, -390, 1, 49]"/>
1276 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_WnZEIGg8Ee25oofngfVl_A" id="(0.22950819672131148,0.5714285714285714)"/> 1247 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_WnZEIGg8Ee25oofngfVl_A" id="(0.22950819672131148,0.5714285714285714)"/>
1277 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_WnZEIWg8Ee25oofngfVl_A" id="(0.5,0.5)"/> 1248 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_WnZEIWg8Ee25oofngfVl_A" id="(0.5,0.5)"/>
1278 </edges> 1249 </edges>
@@ -1324,6 +1295,22 @@
1324 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_mDflxmg8Ee25oofngfVl_A" id="(0.3644067796610169,0.16326530612244897)"/> 1295 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_mDflxmg8Ee25oofngfVl_A" id="(0.3644067796610169,0.16326530612244897)"/>
1325 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_mDflx2g8Ee25oofngfVl_A" id="(0.5,0.5)"/> 1296 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_mDflx2g8Ee25oofngfVl_A" id="(0.5,0.5)"/>
1326 </edges> 1297 </edges>
1298 <edges xmi:type="notation:Edge" xmi:id="_8s7BwDrXEe62Q_vL_UTCsA" type="4001" element="_8sdu2DrXEe62Q_vL_UTCsA" source="_c-HCQKA4EeuqkpDnuik1sg" target="_dzfLYGTvEe2qdtyPWAtoxA">
1299 <children xmi:type="notation:Node" xmi:id="_8s-sIDrXEe62Q_vL_UTCsA" type="6001">
1300 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_8s-sITrXEe62Q_vL_UTCsA" x="-155" y="-60"/>
1301 </children>
1302 <children xmi:type="notation:Node" xmi:id="_8s-sIjrXEe62Q_vL_UTCsA" type="6002">
1303 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_8s-sIzrXEe62Q_vL_UTCsA" x="69" y="10"/>
1304 </children>
1305 <children xmi:type="notation:Node" xmi:id="_8s-sJDrXEe62Q_vL_UTCsA" type="6003">
1306 <layoutConstraint xmi:type="notation:Bounds" xmi:id="_8s-sJTrXEe62Q_vL_UTCsA" x="3" y="10"/>
1307 </children>
1308 <styles xmi:type="notation:ConnectorStyle" xmi:id="_8s7BwTrXEe62Q_vL_UTCsA" routing="Rectilinear"/>
1309 <styles xmi:type="notation:FontStyle" xmi:id="_8s7BwjrXEe62Q_vL_UTCsA" fontColor="7490599" fontName="Noto Sans" fontHeight="8"/>
1310 <bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_8s7BwzrXEe62Q_vL_UTCsA" points="[-52, 40, 0, 542]$[-84, 40, -32, 542]$[-84, -502, -32, 0]"/>
1311 <sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_8s_6QDrXEe62Q_vL_UTCsA" id="(0.3586206896551724,0.0)"/>
1312 <targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_8s_6QTrXEe62Q_vL_UTCsA" id="(0.5338983050847458,1.0)"/>
1313 </edges>
1327 </data> 1314 </data>
1328 </ownedAnnotationEntries> 1315 </ownedAnnotationEntries>
1329 <ownedAnnotationEntries xmi:type="description:AnnotationEntry" uid="_Csiy8KA4EeuqkpDnuik1sg" source="DANNOTATION_CUSTOMIZATION_KEY"> 1316 <ownedAnnotationEntries xmi:type="description:AnnotationEntry" uid="_Csiy8KA4EeuqkpDnuik1sg" source="DANNOTATION_CUSTOMIZATION_KEY">
@@ -1369,7 +1356,7 @@
1369 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/> 1356 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
1370 </ownedElements> 1357 </ownedElements>
1371 </ownedDiagramElements> 1358 </ownedDiagramElements>
1372 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_c-A7oKA4EeuqkpDnuik1sg" name="ReferenceDeclaration" tooltipText="" outgoingEdges="_0V3L1qA4EeuqkpDnuik1sg _p1JWcqBJEeuqkpDnuik1sg _NrtSzmTzEe2qdtyPWAtoxA _bNXJs2g8Ee25oofngfVl_A" incomingEdges="_0V3L1qA4EeuqkpDnuik1sg" width="12" height="10"> 1359 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_c-A7oKA4EeuqkpDnuik1sg" name="ReferenceDeclaration" tooltipText="" outgoingEdges="_0V3L1qA4EeuqkpDnuik1sg _p1JWcqBJEeuqkpDnuik1sg _NrtSzmTzEe2qdtyPWAtoxA _bNXJs2g8Ee25oofngfVl_A _8sdu2DrXEe62Q_vL_UTCsA" incomingEdges="_0V3L1qA4EeuqkpDnuik1sg" width="12" height="10">
1373 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration"/> 1360 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration"/>
1374 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration"/> 1361 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration"/>
1375 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints> 1362 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
@@ -1441,14 +1428,14 @@
1441 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints> 1428 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
1442 <arrangeConstraints>KEEP_SIZE</arrangeConstraints> 1429 <arrangeConstraints>KEEP_SIZE</arrangeConstraints>
1443 <arrangeConstraints>KEEP_RATIO</arrangeConstraints> 1430 <arrangeConstraints>KEEP_RATIO</arrangeConstraints>
1444 <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_jX0uAjNoEe2fD4dIhR_vzA" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216"> 1431 <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_dFvc0DrQEe62Q_vL_UTCsA" borderSize="1" borderSizeComputationExpression="1" backgroundStyle="Liquid" foregroundColor="255,252,216">
1445 <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/> 1432 <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/>
1446 </ownedStyle> 1433 </ownedStyle>
1447 <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/> 1434 <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/>
1448 <ownedElements xmi:type="diagram:DNodeListElement" uid="_ida7QDNoEe2fD4dIhR_vzA" name="kind : PredicateKind = DEFAULT" tooltipText=""> 1435 <ownedElements xmi:type="diagram:DNodeListElement" uid="_blvCoDrQEe62Q_vL_UTCsA" name="error : EBoolean = false" tooltipText="">
1449 <target xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//PredicateDefinition/kind"/> 1436 <target xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//PredicateDefinition/error"/>
1450 <semanticElements xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//PredicateDefinition/kind"/> 1437 <semanticElements xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//PredicateDefinition/error"/>
1451 <ownedStyle xmi:type="diagram:BundledImage" uid="_jX18JDNoEe2fD4dIhR_vzA" labelAlignment="LEFT"> 1438 <ownedStyle xmi:type="diagram:BundledImage" uid="_dFx5EzrQEe62Q_vL_UTCsA" labelAlignment="LEFT">
1452 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']/@style"/> 1439 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']/@style"/>
1453 </ownedStyle> 1440 </ownedStyle>
1454 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/> 1441 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
@@ -1464,14 +1451,6 @@
1464 <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/> 1451 <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@style"/>
1465 </ownedStyle> 1452 </ownedStyle>
1466 <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/> 1453 <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']"/>
1467 <ownedElements xmi:type="diagram:DNodeListElement" uid="_ARaYkBjHEe2_erjsEmF9GQ" name="modality : Modality = DEFAULT" tooltipText="">
1468 <target xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//Parameter/modality"/>
1469 <semanticElements xmi:type="ecore:EAttribute" href="src/main/resources/model/problem.ecore#//Parameter/modality"/>
1470 <ownedStyle xmi:type="diagram:BundledImage" uid="_CsAyxRjHEe2_erjsEmF9GQ" labelAlignment="LEFT">
1471 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']/@style"/>
1472 </ownedStyle>
1473 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EClass']/@subNodeMappings[name='EC%20EAttribute']"/>
1474 </ownedElements>
1475 </ownedDiagramElements> 1454 </ownedDiagramElements>
1476 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_Uy4bWaA6EeuqkpDnuik1sg" name="[0..*] parameters" sourceNode="_A8hIkCrZEeyyC-O0_LlY9w" targetNode="_QKD2EKA6EeuqkpDnuik1sg"> 1455 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_Uy4bWaA6EeuqkpDnuik1sg" name="[0..*] parameters" sourceNode="_A8hIkCrZEeyyC-O0_LlY9w" targetNode="_QKD2EKA6EeuqkpDnuik1sg">
1477 <target xmi:type="ecore:EReference" href="src/main/resources/model/problem.ecore#//ParametricDefinition/parameters"/> 1456 <target xmi:type="ecore:EReference" href="src/main/resources/model/problem.ecore#//ParametricDefinition/parameters"/>
@@ -1940,12 +1919,12 @@
1940 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_ddmjcCrZEeyyC-O0_LlY9w" sourceNode="_fihqUKA5EeuqkpDnuik1sg" targetNode="_A8hIkCrZEeyyC-O0_LlY9w"> 1919 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_ddmjcCrZEeyyC-O0_LlY9w" sourceNode="_fihqUKA5EeuqkpDnuik1sg" targetNode="_A8hIkCrZEeyyC-O0_LlY9w">
1941 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/> 1920 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/>
1942 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/> 1921 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/>
1943 <ownedStyle xmi:type="diagram:EdgeStyle" uid="_jX7b1DNoEe2fD4dIhR_vzA" lineStyle="dash" targetArrow="InputClosedArrow" routingStyle="tree"> 1922 <ownedStyle xmi:type="diagram:EdgeStyle" uid="_dF7qIjrQEe62Q_vL_UTCsA" lineStyle="dash" targetArrow="InputClosedArrow" routingStyle="tree">
1944 <description xmi:type="style:EdgeStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']/@conditionnalStyles.0/@style"/> 1923 <description xmi:type="style:EdgeStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']/@conditionnalStyles.0/@style"/>
1945 <beginLabelStyle xmi:type="diagram:BeginLabelStyle" uid="_jX7b1TNoEe2fD4dIhR_vzA" showIcon="false"> 1924 <beginLabelStyle xmi:type="diagram:BeginLabelStyle" uid="_dF7qIzrQEe62Q_vL_UTCsA" showIcon="false">
1946 <labelFormat>italic</labelFormat> 1925 <labelFormat>italic</labelFormat>
1947 </beginLabelStyle> 1926 </beginLabelStyle>
1948 <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_jX7b1jNoEe2fD4dIhR_vzA" showIcon="false"/> 1927 <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_dF7qJDrQEe62Q_vL_UTCsA" showIcon="false"/>
1949 </ownedStyle> 1928 </ownedStyle>
1950 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']"/> 1929 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']"/>
1951 </ownedDiagramElements> 1930 </ownedDiagramElements>
@@ -2100,49 +2079,6 @@
2100 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/> 2079 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2101 </ownedElements> 2080 </ownedElements>
2102 </ownedDiagramElements> 2081 </ownedDiagramElements>
2103 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_DD1pQDNoEe2fD4dIhR_vzA" name="PredicateKind" tooltipText="" width="12" height="10">
2104 <target xmi:type="ecore:EEnum" href="src/main/resources/model/problem.ecore#//PredicateKind"/>
2105 <semanticElements xmi:type="ecore:EEnum" href="src/main/resources/model/problem.ecore#//PredicateKind"/>
2106 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
2107 <arrangeConstraints>KEEP_SIZE</arrangeConstraints>
2108 <arrangeConstraints>KEEP_RATIO</arrangeConstraints>
2109 <ownedStyle xmi:type="diagram:FlatContainerStyle" uid="_DD1pQTNoEe2fD4dIhR_vzA" borderSize="1" borderSizeComputationExpression="1" borderColor="125,125,125" backgroundStyle="Liquid" foregroundColor="221,236,202">
2110 <description xmi:type="style:FlatContainerStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@style"/>
2111 </ownedStyle>
2112 <actualMapping xmi:type="description_1:ContainerMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']"/>
2113 <ownedElements xmi:type="diagram:DNodeListElement" uid="_FDOuIDNoEe2fD4dIhR_vzA" name="DEFAULT" tooltipText="">
2114 <target xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/DEFAULT"/>
2115 <semanticElements xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/DEFAULT"/>
2116 <ownedStyle xmi:type="diagram:BundledImage" uid="_FDPVMDNoEe2fD4dIhR_vzA" labelAlignment="LEFT">
2117 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']/@style"/>
2118 </ownedStyle>
2119 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2120 </ownedElements>
2121 <ownedElements xmi:type="diagram:DNodeListElement" uid="_F2glMDNoEe2fD4dIhR_vzA" name="ERROR" tooltipText="">
2122 <target xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/ERROR"/>
2123 <semanticElements xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/ERROR"/>
2124 <ownedStyle xmi:type="diagram:BundledImage" uid="_F2hMQDNoEe2fD4dIhR_vzA" labelAlignment="LEFT">
2125 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']/@style"/>
2126 </ownedStyle>
2127 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2128 </ownedElements>
2129 <ownedElements xmi:type="diagram:DNodeListElement" uid="_GS7bcDNoEe2fD4dIhR_vzA" name="CONTAINED" tooltipText="">
2130 <target xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/CONTAINED"/>
2131 <semanticElements xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/CONTAINED"/>
2132 <ownedStyle xmi:type="diagram:BundledImage" uid="_GS7bcTNoEe2fD4dIhR_vzA" labelAlignment="LEFT">
2133 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']/@style"/>
2134 </ownedStyle>
2135 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2136 </ownedElements>
2137 <ownedElements xmi:type="diagram:DNodeListElement" uid="_Gx7EkDNoEe2fD4dIhR_vzA" name="CONTAINMENT" tooltipText="">
2138 <target xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/CONTAINMENT"/>
2139 <semanticElements xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//PredicateKind/CONTAINMENT"/>
2140 <ownedStyle xmi:type="diagram:BundledImage" uid="_Gx7roDNoEe2fD4dIhR_vzA" labelAlignment="LEFT">
2141 <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']/@style"/>
2142 </ownedStyle>
2143 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2144 </ownedElements>
2145 </ownedDiagramElements>
2146 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_78pRMF9mEe2rXNsIDUvqhw" name="FunctionDefinition" tooltipText="" outgoingEdges="_rKoQHF9nEe2rXNsIDUvqhw _S6YCJl9wEe2rXNsIDUvqhw _sMPaBmTvEe2qdtyPWAtoxA" width="12" height="10"> 2082 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_78pRMF9mEe2rXNsIDUvqhw" name="FunctionDefinition" tooltipText="" outgoingEdges="_rKoQHF9nEe2rXNsIDUvqhw _S6YCJl9wEe2rXNsIDUvqhw _sMPaBmTvEe2qdtyPWAtoxA" width="12" height="10">
2147 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/> 2083 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/>
2148 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/> 2084 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/>
@@ -2349,7 +2285,7 @@
2349 </ownedStyle> 2285 </ownedStyle>
2350 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']"/> 2286 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']"/>
2351 </ownedDiagramElements> 2287 </ownedDiagramElements>
2352 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_dzVaYGTvEe2qdtyPWAtoxA" name="Relation" tooltipText="" outgoingEdges="_-OYJtGTvEe2qdtyPWAtoxA" incomingEdges="_m2GbcmTvEe2qdtyPWAtoxA _nxr57GTvEe2qdtyPWAtoxA _pHlWJ2TvEe2qdtyPWAtoxA _p-1uSmTvEe2qdtyPWAtoxA _sMPaBmTvEe2qdtyPWAtoxA _NrtSzmTzEe2qdtyPWAtoxA _YWqZhmTzEe2qdtyPWAtoxA _iWzpAmTzEe2qdtyPWAtoxA _q604amTzEe2qdtyPWAtoxA" width="12" height="10"> 2288 <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_dzVaYGTvEe2qdtyPWAtoxA" name="Relation" tooltipText="" outgoingEdges="_-OYJtGTvEe2qdtyPWAtoxA" incomingEdges="_m2GbcmTvEe2qdtyPWAtoxA _nxr57GTvEe2qdtyPWAtoxA _pHlWJ2TvEe2qdtyPWAtoxA _p-1uSmTvEe2qdtyPWAtoxA _sMPaBmTvEe2qdtyPWAtoxA _NrtSzmTzEe2qdtyPWAtoxA _YWqZhmTzEe2qdtyPWAtoxA _iWzpAmTzEe2qdtyPWAtoxA _q604amTzEe2qdtyPWAtoxA _8sdu2DrXEe62Q_vL_UTCsA" width="12" height="10">
2353 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//Relation"/> 2289 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//Relation"/>
2354 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//Relation"/> 2290 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//Relation"/>
2355 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints> 2291 <arrangeConstraints>KEEP_LOCATION</arrangeConstraints>
@@ -2400,12 +2336,12 @@
2400 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_p-1uSmTvEe2qdtyPWAtoxA" sourceNode="_fihqUKA5EeuqkpDnuik1sg" targetNode="_dzVaYGTvEe2qdtyPWAtoxA"> 2336 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_p-1uSmTvEe2qdtyPWAtoxA" sourceNode="_fihqUKA5EeuqkpDnuik1sg" targetNode="_dzVaYGTvEe2qdtyPWAtoxA">
2401 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/> 2337 <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/>
2402 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/> 2338 <semanticElements xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//PredicateDefinition"/>
2403 <ownedStyle xmi:type="diagram:EdgeStyle" uid="_p-1uS2TvEe2qdtyPWAtoxA" targetArrow="InputClosedArrow" routingStyle="tree"> 2339 <ownedStyle xmi:type="diagram:EdgeStyle" uid="_dF7qKDrQEe62Q_vL_UTCsA" targetArrow="InputClosedArrow" routingStyle="tree">
2404 <description xmi:type="style:EdgeStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']/@style"/> 2340 <description xmi:type="style:EdgeStyleDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']/@style"/>
2405 <beginLabelStyle xmi:type="diagram:BeginLabelStyle" uid="_p-1uTGTvEe2qdtyPWAtoxA" showIcon="false"> 2341 <beginLabelStyle xmi:type="diagram:BeginLabelStyle" uid="_dF7qKTrQEe62Q_vL_UTCsA" showIcon="false">
2406 <labelFormat>italic</labelFormat> 2342 <labelFormat>italic</labelFormat>
2407 </beginLabelStyle> 2343 </beginLabelStyle>
2408 <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_p-1uTWTvEe2qdtyPWAtoxA" showIcon="false"/> 2344 <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_dF7qKjrQEe62Q_vL_UTCsA" showIcon="false"/>
2409 </ownedStyle> 2345 </ownedStyle>
2410 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']"/> 2346 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC%20ESupertypes']"/>
2411 </ownedDiagramElements> 2347 </ownedDiagramElements>
@@ -2611,6 +2547,19 @@
2611 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/> 2547 <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/>
2612 </ownedElements> 2548 </ownedElements>
2613 </ownedDiagramElements> 2549 </ownedDiagramElements>
2550 <ownedDiagramElements xmi:type="diagram:DEdge" uid="_8sdu2DrXEe62Q_vL_UTCsA" name="[0..1] invalidMultiplicity" sourceNode="_c-A7oKA4EeuqkpDnuik1sg" targetNode="_dzVaYGTvEe2qdtyPWAtoxA">
2551 <target xmi:type="ecore:EReference" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration/invalidMultiplicity"/>
2552 <semanticElements xmi:type="ecore:EReference" href="src/main/resources/model/problem.ecore#//ReferenceDeclaration/invalidMultiplicity"/>
2553 <ownedStyle xmi:type="diagram:EdgeStyle" uid="_8seV0DrXEe62Q_vL_UTCsA" description="_L-JhMKA4EeuqkpDnuik1sg" sourceArrow="FillDiamond" routingStyle="manhattan" strokeColor="0,0,0">
2554 <centerLabelStyle xmi:type="diagram:CenterLabelStyle" uid="_8seV0jrXEe62Q_vL_UTCsA" showIcon="false">
2555 <customFeatures>labelSize</customFeatures>
2556 </centerLabelStyle>
2557 <endLabelStyle xmi:type="diagram:EndLabelStyle" uid="_8seV0TrXEe62Q_vL_UTCsA" showIcon="false" labelColor="39,76,114">
2558 <customFeatures>labelSize</customFeatures>
2559 </endLabelStyle>
2560 </ownedStyle>
2561 <actualMapping xmi:type="description_1:EdgeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@edgeMappings[name='EC_EReference']"/>
2562 </ownedDiagramElements>
2614 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/> 2563 <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/>
2615 <filterVariableHistory xmi:type="diagram:FilterVariableHistory" uid="_CsWlsKA4EeuqkpDnuik1sg"/> 2564 <filterVariableHistory xmi:type="diagram:FilterVariableHistory" uid="_CsWlsKA4EeuqkpDnuik1sg"/>
2616 <activatedLayers xmi:type="description_1:Layer" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer"/> 2565 <activatedLayers xmi:type="description_1:Layer" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer"/>
diff --git a/subprojects/language-model/src/main/resources/model/problem.ecore b/subprojects/language-model/src/main/resources/model/problem.ecore
index 2d86382d..204f922c 100644
--- a/subprojects/language-model/src/main/resources/model/problem.ecore
+++ b/subprojects/language-model/src/main/resources/model/problem.ecore
@@ -22,17 +22,18 @@
22 containment="true"/> 22 containment="true"/>
23 <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/> 23 <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//ReferenceKind"/>
24 <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/> 24 <eStructuralFeatures xsi:type="ecore:EReference" name="referenceType" eType="#//Relation"/>
25 <eStructuralFeatures xsi:type="ecore:EReference" name="invalidMultiplicity" eType="#//Relation"
26 containment="true"/>
25 </eClassifiers> 27 </eClassifiers>
26 <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true"> 28 <eClassifiers xsi:type="ecore:EClass" name="NamedElement" abstract="true">
27 <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> 29 <eStructuralFeatures xsi:type="ecore:EAttribute" name="name" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
28 </eClassifiers> 30 </eClassifiers>
29 <eClassifiers xsi:type="ecore:EClass" name="PredicateDefinition" eSuperTypes="#//ParametricDefinition #//Relation"> 31 <eClassifiers xsi:type="ecore:EClass" name="PredicateDefinition" eSuperTypes="#//ParametricDefinition #//Relation">
30 <eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//PredicateKind"/>
31 <eStructuralFeatures xsi:type="ecore:EReference" name="bodies" upperBound="-1" 32 <eStructuralFeatures xsi:type="ecore:EReference" name="bodies" upperBound="-1"
32 eType="#//Conjunction" containment="true"/> 33 eType="#//Conjunction" containment="true"/>
34 <eStructuralFeatures xsi:type="ecore:EAttribute" name="error" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"/>
33 </eClassifiers> 35 </eClassifiers>
34 <eClassifiers xsi:type="ecore:EClass" name="Parameter" eSuperTypes="#//Variable"> 36 <eClassifiers xsi:type="ecore:EClass" name="Parameter" eSuperTypes="#//Variable">
35 <eStructuralFeatures xsi:type="ecore:EAttribute" name="modality" eType="#//Modality"/>
36 <eStructuralFeatures xsi:type="ecore:EReference" name="parameterType" eType="#//Relation"/> 37 <eStructuralFeatures xsi:type="ecore:EReference" name="parameterType" eType="#//Relation"/>
37 </eClassifiers> 38 </eClassifiers>
38 <eClassifiers xsi:type="ecore:EClass" name="Variable" abstract="true" eSuperTypes="#//VariableOrNode"/> 39 <eClassifiers xsi:type="ecore:EClass" name="Variable" abstract="true" eSuperTypes="#//VariableOrNode"/>
@@ -169,12 +170,6 @@
169 <eLiterals name="CONTAINMENT" value="2"/> 170 <eLiterals name="CONTAINMENT" value="2"/>
170 <eLiterals name="CONTAINER" value="3"/> 171 <eLiterals name="CONTAINER" value="3"/>
171 </eClassifiers> 172 </eClassifiers>
172 <eClassifiers xsi:type="ecore:EEnum" name="PredicateKind">
173 <eLiterals name="DEFAULT"/>
174 <eLiterals name="ERROR" value="1"/>
175 <eLiterals name="CONTAINED" value="2"/>
176 <eLiterals name="CONTAINMENT" value="3"/>
177 </eClassifiers>
178 <eClassifiers xsi:type="ecore:EClass" name="Expr" abstract="true"/> 173 <eClassifiers xsi:type="ecore:EClass" name="Expr" abstract="true"/>
179 <eClassifiers xsi:type="ecore:EClass" name="VariableOrNodeExpr" eSuperTypes="#//Expr"> 174 <eClassifiers xsi:type="ecore:EClass" name="VariableOrNodeExpr" eSuperTypes="#//Expr">
180 <eStructuralFeatures xsi:type="ecore:EReference" name="variableOrNode" eType="#//VariableOrNode"/> 175 <eStructuralFeatures xsi:type="ecore:EReference" name="variableOrNode" eType="#//VariableOrNode"/>
diff --git a/subprojects/language-model/src/main/resources/model/problem.genmodel b/subprojects/language-model/src/main/resources/model/problem.genmodel
index 5767d18d..b5aa81c4 100644
--- a/subprojects/language-model/src/main/resources/model/problem.genmodel
+++ b/subprojects/language-model/src/main/resources/model/problem.genmodel
@@ -40,12 +40,6 @@
40 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINMENT"/> 40 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINMENT"/>
41 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINER"/> 41 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINER"/>
42 </genEnums> 42 </genEnums>
43 <genEnums typeSafeEnumCompatible="false" ecoreEnum="problem.ecore#//PredicateKind">
44 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//PredicateKind/DEFAULT"/>
45 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//PredicateKind/ERROR"/>
46 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//PredicateKind/CONTAINED"/>
47 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//PredicateKind/CONTAINMENT"/>
48 </genEnums>
49 <genEnums typeSafeEnumCompatible="false" ecoreEnum="problem.ecore#//UnaryOp"> 43 <genEnums typeSafeEnumCompatible="false" ecoreEnum="problem.ecore#//UnaryOp">
50 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/PLUS"/> 44 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/PLUS"/>
51 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/MINUS"/> 45 <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/MINUS"/>
@@ -85,16 +79,16 @@
85 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference problem.ecore#//ReferenceDeclaration/multiplicity"/> 79 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference problem.ecore#//ReferenceDeclaration/multiplicity"/>
86 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//ReferenceDeclaration/kind"/> 80 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//ReferenceDeclaration/kind"/>
87 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//ReferenceDeclaration/referenceType"/> 81 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//ReferenceDeclaration/referenceType"/>
82 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference problem.ecore#//ReferenceDeclaration/invalidMultiplicity"/>
88 </genClasses> 83 </genClasses>
89 <genClasses ecoreClass="problem.ecore#//NamedElement"> 84 <genClasses ecoreClass="problem.ecore#//NamedElement">
90 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//NamedElement/name"/> 85 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//NamedElement/name"/>
91 </genClasses> 86 </genClasses>
92 <genClasses ecoreClass="problem.ecore#//PredicateDefinition"> 87 <genClasses ecoreClass="problem.ecore#//PredicateDefinition">
93 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//PredicateDefinition/kind"/>
94 <genFeatures children="true" createChild="true" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//PredicateDefinition/bodies"/> 88 <genFeatures children="true" createChild="true" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//PredicateDefinition/bodies"/>
89 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//PredicateDefinition/error"/>
95 </genClasses> 90 </genClasses>
96 <genClasses ecoreClass="problem.ecore#//Parameter"> 91 <genClasses ecoreClass="problem.ecore#//Parameter">
97 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute problem.ecore#//Parameter/modality"/>
98 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//Parameter/parameterType"/> 92 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference problem.ecore#//Parameter/parameterType"/>
99 </genClasses> 93 </genClasses>
100 <genClasses ecoreClass="problem.ecore#//Variable"/> 94 <genClasses ecoreClass="problem.ecore#//Variable"/>
diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts
index 38cd9e0d..4374f78c 100644
--- a/subprojects/language-semantics/build.gradle.kts
+++ b/subprojects/language-semantics/build.gradle.kts
@@ -9,9 +9,11 @@ plugins {
9} 9}
10 10
11dependencies { 11dependencies {
12 implementation(libs.eclipseCollections) 12 api(libs.eclipseCollections.api)
13 implementation(libs.eclipseCollections.api)
14 api(project(":refinery-language")) 13 api(project(":refinery-language"))
15 api(project(":refinery-store")) 14 api(project(":refinery-store"))
16 testImplementation(testFixtures(project(":refinery-language"))) 15 api(project(":refinery-store-query"))
16 api(project(":refinery-store-reasoning"))
17 implementation(project(":refinery-store-reasoning-scope"))
18 runtimeOnly(libs.eclipseCollections)
17} 19}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/BuiltInDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/BuiltInDetail.java
new file mode 100644
index 00000000..6f706069
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/BuiltInDetail.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record BuiltInDetail() implements RelationDetail {
9 public static final BuiltInDetail INSTANCE = new BuiltInDetail();
10}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ClassDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ClassDetail.java
new file mode 100644
index 00000000..1d3190f5
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ClassDetail.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record ClassDetail(boolean abstractClass) implements RelationDetail {
9 public static final ClassDetail CONCRETE_CLASS = new ClassDetail(false);
10
11 public static final ClassDetail ABSTRACT_CLASS = new ClassDetail(true);
12
13 public static ClassDetail ofAbstractClass(boolean abstractClass) {
14 return abstractClass ? ABSTRACT_CLASS : CONCRETE_CLASS;
15 }
16}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/Metadata.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/Metadata.java
new file mode 100644
index 00000000..d2dcb43a
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/Metadata.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public sealed interface Metadata permits NodeMetadata, RelationMetadata {
9 String name();
10
11 String simpleName();
12}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java
new file mode 100644
index 00000000..0c18b1b3
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java
@@ -0,0 +1,181 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8import com.google.inject.Inject;
9import org.eclipse.emf.ecore.EObject;
10import org.eclipse.xtext.naming.IQualifiedNameConverter;
11import org.eclipse.xtext.naming.IQualifiedNameProvider;
12import org.eclipse.xtext.naming.QualifiedName;
13import org.eclipse.xtext.scoping.IScope;
14import org.eclipse.xtext.scoping.IScopeProvider;
15import tools.refinery.language.model.problem.*;
16import tools.refinery.language.semantics.model.ModelInitializer;
17import tools.refinery.language.semantics.model.TracedException;
18import tools.refinery.language.utils.ProblemUtil;
19import tools.refinery.store.reasoning.representation.PartialRelation;
20
21import java.util.*;
22
23public class MetadataCreator {
24 @Inject
25 private IScopeProvider scopeProvider;
26
27 @Inject
28 private IQualifiedNameProvider qualifiedNameProvider;
29
30 @Inject
31 private IQualifiedNameConverter qualifiedNameConverter;
32
33 private ModelInitializer initializer;
34
35 private IScope nodeScope;
36
37 private IScope relationScope;
38
39 public void setInitializer(ModelInitializer initializer) {
40 if (initializer == null) {
41 throw new IllegalArgumentException("Initializer was already set");
42 }
43 this.initializer = initializer;
44 var problem = initializer.getProblem();
45 nodeScope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
46 relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
47 }
48
49 public List<NodeMetadata> getNodesMetadata() {
50 var nodes = new NodeMetadata[initializer.getNodeCount()];
51 for (var entry : initializer.getNodeTrace().keyValuesView()) {
52 var node = entry.getOne();
53 var id = entry.getTwo();
54 nodes[id] = getNodeMetadata(node);
55 }
56 return List.of(nodes);
57 }
58
59 private NodeMetadata getNodeMetadata(Node node) {
60 var qualifiedName = getQualifiedName(node);
61 var simpleName = getSimpleName(node, qualifiedName, nodeScope);
62 return new NodeMetadata(qualifiedNameConverter.toString(qualifiedName),
63 qualifiedNameConverter.toString(simpleName), getNodeKind(node));
64 }
65
66 private NodeKind getNodeKind(Node node) {
67 if (ProblemUtil.isImplicitNode(node)) {
68 return NodeKind.IMPLICIT;
69 } else if (ProblemUtil.isIndividualNode(node)) {
70 return NodeKind.INDIVIDUAL;
71 } else if (ProblemUtil.isNewNode(node)) {
72 return NodeKind.NEW;
73 } else {
74 throw new TracedException(node, "Unknown node type");
75 }
76 }
77
78 public List<RelationMetadata> getRelationsMetadata() {
79 var relationTrace = initializer.getRelationTrace();
80 var relations = new ArrayList<RelationMetadata>(relationTrace.size());
81 for (var entry : relationTrace.entrySet()) {
82 var relation = entry.getKey();
83 var partialRelation = entry.getValue();
84 var metadata = getRelationMetadata(relation, partialRelation);
85 relations.add(metadata);
86 }
87 return Collections.unmodifiableList(relations);
88 }
89
90 private RelationMetadata getRelationMetadata(Relation relation, PartialRelation partialRelation) {
91 var qualifiedName = getQualifiedName(relation);
92 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
93 var simpleName = getSimpleName(relation, qualifiedName, relationScope);
94 var simpleNameString = qualifiedNameConverter.toString(simpleName);
95 var arity = partialRelation.arity();
96 var detail = getRelationDetail(relation, partialRelation);
97 return new RelationMetadata(qualifiedNameString, simpleNameString, arity, detail);
98 }
99
100 private RelationDetail getRelationDetail(Relation relation, PartialRelation partialRelation) {
101 if (ProblemUtil.isBuiltIn(relation) && !ProblemUtil.isError(relation)) {
102 return getBuiltInDetail();
103 }
104 if (relation instanceof ClassDeclaration classDeclaration) {
105 return getClassDetail(classDeclaration);
106 } else if (relation instanceof ReferenceDeclaration) {
107 return getReferenceDetail(partialRelation);
108 } else if (relation instanceof EnumDeclaration) {
109 return getEnumDetail();
110 } else if (relation instanceof PredicateDefinition predicateDefinition) {
111 return getPredicateDetail(predicateDefinition);
112 } else {
113 throw new TracedException(relation, "Unknown relation");
114 }
115 }
116
117 private RelationDetail getBuiltInDetail() {
118 return BuiltInDetail.INSTANCE;
119 }
120
121 private RelationDetail getClassDetail(ClassDeclaration classDeclaration) {
122 return ClassDetail.ofAbstractClass(classDeclaration.isAbstract());
123 }
124
125 private RelationDetail getReferenceDetail(PartialRelation partialRelation) {
126 var metamodel = initializer.getMetamodel();
127 var opposite = metamodel.oppositeReferences().get(partialRelation);
128 if (opposite == null) {
129 boolean isContainment = metamodel.containmentHierarchy().containsKey(partialRelation);
130 return ReferenceDetail.ofContainment(isContainment);
131 } else {
132 boolean isContainer = metamodel.containmentHierarchy().containsKey(opposite);
133 return new OppositeReferenceDetail(isContainer, opposite.name());
134 }
135 }
136
137 private RelationDetail getEnumDetail() {
138 return ClassDetail.CONCRETE_CLASS;
139 }
140
141 private RelationDetail getPredicateDetail(PredicateDefinition predicate) {
142 return PredicateDetail.ofError(predicate.isError());
143 }
144
145 private QualifiedName getQualifiedName(EObject eObject) {
146 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(eObject);
147 if (qualifiedName == null) {
148 throw new TracedException(eObject, "Unknown qualified name");
149 }
150 return qualifiedName;
151 }
152
153 private QualifiedName getSimpleName(EObject eObject, QualifiedName qualifiedName, IScope scope) {
154 var descriptions = scope.getElements(eObject);
155 var names = new HashSet<QualifiedName>();
156 for (var description : descriptions) {
157 // {@code getQualifiedName()} will refer to the full name for objects that are loaded from the global
158 // scope, but {@code getName()} returns the qualified name that we set in
159 // {@code ProblemResourceDescriptionStrategy}.
160 names.add(description.getName());
161 }
162 var iterator = names.stream().sorted(Comparator.comparingInt(QualifiedName::getSegmentCount)).iterator();
163 while (iterator.hasNext()) {
164 var simpleName = iterator.next();
165 if (names.contains(simpleName) && isUnique(scope, simpleName)) {
166 return simpleName;
167 }
168 }
169 throw new TracedException(eObject, "Ambiguous qualified name: " +
170 qualifiedNameConverter.toString(qualifiedName));
171 }
172
173 private boolean isUnique(IScope scope, QualifiedName name) {
174 var iterator = scope.getElements(name).iterator();
175 if (!iterator.hasNext()) {
176 return false;
177 }
178 iterator.next();
179 return !iterator.hasNext();
180 }
181}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeKind.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeKind.java
new file mode 100644
index 00000000..01f0cd09
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeKind.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public enum NodeKind {
9 IMPLICIT,
10 INDIVIDUAL,
11 NEW
12}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeMetadata.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeMetadata.java
new file mode 100644
index 00000000..812952c0
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/NodeMetadata.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record NodeMetadata(String name, String simpleName, NodeKind kind) implements Metadata {
9}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/OppositeReferenceDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/OppositeReferenceDetail.java
new file mode 100644
index 00000000..26d7461c
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/OppositeReferenceDetail.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record OppositeReferenceDetail(boolean container, String opposite) implements RelationDetail {
9}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/PredicateDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/PredicateDetail.java
new file mode 100644
index 00000000..ca397eca
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/PredicateDetail.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record PredicateDetail(boolean error) implements RelationDetail {
9 public static final PredicateDetail PREDICATE = new PredicateDetail(false);
10
11 public static final PredicateDetail ERROR_PREDICATE = new PredicateDetail(true);
12
13 public static PredicateDetail ofError(boolean error) {
14 return error ? ERROR_PREDICATE : PREDICATE;
15 }
16}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ReferenceDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ReferenceDetail.java
new file mode 100644
index 00000000..36771566
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/ReferenceDetail.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record ReferenceDetail(boolean containment) implements RelationDetail {
9 public static final ReferenceDetail CROSS_REFERENCE = new ReferenceDetail(false);
10
11 public static final ReferenceDetail CONTAINMENT_REFERENCE = new ReferenceDetail(true);
12
13 public static ReferenceDetail ofContainment(boolean containment) {
14 return containment ? CONTAINMENT_REFERENCE : CROSS_REFERENCE;
15 }
16}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationDetail.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationDetail.java
new file mode 100644
index 00000000..105179fd
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationDetail.java
@@ -0,0 +1,10 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public sealed interface RelationDetail permits ClassDetail, ReferenceDetail, PredicateDetail, OppositeReferenceDetail,
9 BuiltInDetail {
10}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationMetadata.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationMetadata.java
new file mode 100644
index 00000000..5abcc253
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/RelationMetadata.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.metadata;
7
8public record RelationMetadata(String name, String simpleName, int arity, RelationDetail detail) implements Metadata {
9}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
index 06b8ad77..89c41a8e 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/ModelInitializer.java
@@ -9,67 +9,399 @@ import com.google.inject.Inject;
9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps; 9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap; 10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
11import tools.refinery.language.model.problem.*; 11import tools.refinery.language.model.problem.*;
12import tools.refinery.language.semantics.model.internal.DecisionTree; 12import tools.refinery.language.semantics.model.internal.MutableSeed;
13import tools.refinery.language.utils.BuiltinSymbols;
13import tools.refinery.language.utils.ProblemDesugarer; 14import tools.refinery.language.utils.ProblemDesugarer;
14import tools.refinery.language.utils.RelationInfo; 15import tools.refinery.language.utils.ProblemUtil;
15import tools.refinery.store.representation.Symbol; 16import tools.refinery.store.model.ModelStoreBuilder;
17import tools.refinery.store.query.Constraint;
18import tools.refinery.store.query.dnf.InvalidClauseException;
19import tools.refinery.store.query.dnf.Query;
20import tools.refinery.store.query.dnf.RelationalQuery;
21import tools.refinery.store.query.literal.*;
22import tools.refinery.store.query.term.NodeVariable;
23import tools.refinery.store.query.term.Variable;
24import tools.refinery.store.reasoning.ReasoningAdapter;
25import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
26import tools.refinery.store.reasoning.representation.PartialRelation;
27import tools.refinery.store.reasoning.scope.ScopePropagatorBuilder;
28import tools.refinery.store.reasoning.seed.ModelSeed;
29import tools.refinery.store.reasoning.seed.Seed;
30import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
31import tools.refinery.store.reasoning.translator.metamodel.Metamodel;
32import tools.refinery.store.reasoning.translator.metamodel.MetamodelBuilder;
33import tools.refinery.store.reasoning.translator.metamodel.MetamodelTranslator;
34import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
35import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
36import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
37import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
38import tools.refinery.store.reasoning.translator.predicate.PredicateTranslator;
16import tools.refinery.store.representation.TruthValue; 39import tools.refinery.store.representation.TruthValue;
40import tools.refinery.store.representation.cardinality.CardinalityInterval;
41import tools.refinery.store.representation.cardinality.CardinalityIntervals;
42import tools.refinery.store.representation.cardinality.UpperCardinalities;
17import tools.refinery.store.tuple.Tuple; 43import tools.refinery.store.tuple.Tuple;
18 44
19import java.util.HashMap; 45import java.util.*;
20import java.util.Map;
21 46
22public class ModelInitializer { 47public class ModelInitializer {
23 @Inject 48 @Inject
24 private ProblemDesugarer desugarer; 49 private ProblemDesugarer desugarer;
25 50
51 @Inject
52 private SemanticsUtils semanticsUtils;
53
54 private Problem problem;
55
56 private ModelStoreBuilder storeBuilder;
57
58 private BuiltinSymbols builtinSymbols;
59
60 private PartialRelation nodeRelation;
61
26 private final MutableObjectIntMap<Node> nodeTrace = ObjectIntMaps.mutable.empty(); 62 private final MutableObjectIntMap<Node> nodeTrace = ObjectIntMaps.mutable.empty();
27 63
28 private final Map<tools.refinery.language.model.problem.Relation, Symbol<TruthValue>> relationTrace = 64 private final Map<Relation, RelationInfo> relationInfoMap = new LinkedHashMap<>();
29 new HashMap<>(); 65
66 private final Map<PartialRelation, RelationInfo> partialRelationInfoMap = new HashMap<>();
67
68 private final Map<AnyPartialSymbol, Relation> inverseTrace = new HashMap<>();
69
70 private Map<Relation, PartialRelation> relationTrace;
71
72 private final MetamodelBuilder metamodelBuilder = Metamodel.builder();
30 73
31 private int nodeCount = 0; 74 private Metamodel metamodel;
75
76 private Map<Tuple, CardinalityInterval> countSeed = new LinkedHashMap<>();
77
78 private ModelSeed modelSeed;
79
80 public Problem getProblem() {
81 return problem;
82 }
83
84 public int getNodeCount() {
85 return nodeTrace.size();
86 }
87
88 public MutableObjectIntMap<Node> getNodeTrace() {
89 return nodeTrace;
90 }
91
92 public Map<Relation, PartialRelation> getRelationTrace() {
93 return relationTrace;
94 }
32 95
33 public void createModel(Problem problem) { 96 public Relation getInverseTrace(AnyPartialSymbol partialRelation) {
34 var builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException( 97 return inverseTrace.get(partialRelation);
98 }
99
100 public Metamodel getMetamodel() {
101 return metamodel;
102 }
103
104 public ModelSeed createModel(Problem problem, ModelStoreBuilder storeBuilder) {
105 this.problem = problem;
106 this.storeBuilder = storeBuilder;
107 builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException(
35 "Problem has no builtin library")); 108 "Problem has no builtin library"));
36 var collectedSymbols = desugarer.collectSymbols(problem); 109 var nodeInfo = collectPartialRelation(builtinSymbols.node(), 1, TruthValue.TRUE, TruthValue.TRUE);
37 for (var node : collectedSymbols.nodes().keySet()) { 110 nodeRelation = nodeInfo.partialRelation();
38 nodeTrace.put(node, nodeCount); 111 metamodelBuilder.type(nodeRelation);
39 nodeCount += 1; 112 putRelationInfo(builtinSymbols.exists(), new RelationInfo(ReasoningAdapter.EXISTS_SYMBOL, null,
40 } 113 TruthValue.TRUE));
41 for (var pair : collectedSymbols.relations().entrySet()) { 114 putRelationInfo(builtinSymbols.equals(), new RelationInfo(ReasoningAdapter.EQUALS_SYMBOL,
42 var relation = pair.getKey(); 115 (TruthValue) null,
43 var relationInfo = pair.getValue(); 116 null));
44 var isEqualsRelation = relation == builtinSymbols.equals(); 117 putRelationInfo(builtinSymbols.contained(), new RelationInfo(ContainmentHierarchyTranslator.CONTAINED_SYMBOL,
45 var decisionTree = mergeAssertions(relationInfo, isEqualsRelation); 118 null, TruthValue.UNKNOWN));
46 var defaultValue = isEqualsRelation ? TruthValue.FALSE : TruthValue.UNKNOWN; 119 putRelationInfo(builtinSymbols.contains(), new RelationInfo(ContainmentHierarchyTranslator.CONTAINS_SYMBOL,
47 relationTrace.put(relation, Symbol.of( 120 null, TruthValue.UNKNOWN));
48 relationInfo.name(), relationInfo.arity(), TruthValue.class, defaultValue)); 121 putRelationInfo(builtinSymbols.invalidContainer(),
49 } 122 new RelationInfo(ContainmentHierarchyTranslator.INVALID_CONTAINER, TruthValue.FALSE,
50 } 123 TruthValue.FALSE));
51 124 collectNodes();
52 private DecisionTree mergeAssertions(RelationInfo relationInfo, boolean isEqualsRelation) { 125 collectPartialSymbols();
53 var arity = relationInfo.arity(); 126 collectMetamodel();
54 var defaultAssertions = new DecisionTree(arity, isEqualsRelation ? null : TruthValue.UNKNOWN); 127 metamodel = metamodelBuilder.build();
55 var assertions = new DecisionTree(arity); 128 collectAssertions();
56 for (var assertion : relationInfo.assertions()) { 129 storeBuilder.with(new MultiObjectTranslator());
57 var tuple = getTuple(assertion); 130 storeBuilder.with(new MetamodelTranslator(metamodel));
58 var value = getTruthValue(assertion.getValue()); 131 relationTrace = new LinkedHashMap<>(relationInfoMap.size());
59 if (assertion.isDefault()) { 132 int nodeCount = getNodeCount();
60 defaultAssertions.mergeValue(tuple, value); 133 var modelSeedBuilder = ModelSeed.builder(nodeCount);
134 for (var entry : relationInfoMap.entrySet()) {
135 var relation = entry.getKey();
136 var info = entry.getValue();
137 var partialRelation = info.partialRelation();
138 relationTrace.put(relation, partialRelation);
139 modelSeedBuilder.seed(partialRelation, info.toSeed(nodeCount));
140 }
141 collectScopes();
142 modelSeedBuilder.seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
143 .reducedValue(CardinalityIntervals.SET)
144 .putAll(countSeed));
145 modelSeed = modelSeedBuilder.build();
146 collectPredicates();
147 return modelSeed;
148 }
149
150 private void collectNodes() {
151 for (var statement : problem.getStatements()) {
152 if (statement instanceof IndividualDeclaration individualDeclaration) {
153 for (var individual : individualDeclaration.getNodes()) {
154 collectNode(individual);
155 }
156 } else if (statement instanceof ClassDeclaration classDeclaration) {
157 var newNode = classDeclaration.getNewNode();
158 if (newNode != null) {
159 collectNode(newNode);
160 }
161 } else if (statement instanceof EnumDeclaration enumDeclaration) {
162 for (var literal : enumDeclaration.getLiterals()) {
163 collectNode(literal);
164 }
165 }
166 }
167 for (var node : problem.getNodes()) {
168 collectNode(node);
169 }
170 }
171
172 private void collectNode(Node node) {
173 nodeTrace.getIfAbsentPut(node, this::getNodeCount);
174 }
175
176 private void collectPartialSymbols() {
177 for (var statement : problem.getStatements()) {
178 if (statement instanceof ClassDeclaration classDeclaration) {
179 collectClassDeclarationSymbols(classDeclaration);
180 } else if (statement instanceof EnumDeclaration enumDeclaration) {
181 collectPartialRelation(enumDeclaration, 1, TruthValue.FALSE, TruthValue.FALSE);
182 } else if (statement instanceof PredicateDefinition predicateDefinition) {
183 collectPredicateDefinitionSymbol(predicateDefinition);
184 }
185 }
186 }
187
188 private void collectClassDeclarationSymbols(ClassDeclaration classDeclaration) {
189 collectPartialRelation(classDeclaration, 1, null, TruthValue.UNKNOWN);
190 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
191 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
192 collectPartialRelation(referenceDeclaration, 2, null, TruthValue.UNKNOWN);
193 var invalidMultiplicityConstraint = referenceDeclaration.getInvalidMultiplicity();
194 if (invalidMultiplicityConstraint != null) {
195 collectPartialRelation(invalidMultiplicityConstraint, 1, TruthValue.FALSE, TruthValue.FALSE);
196 }
61 } else { 197 } else {
62 assertions.mergeValue(tuple, value); 198 throw new TracedException(featureDeclaration, "Unknown feature declaration");
199 }
200 }
201 }
202
203 private void collectPredicateDefinitionSymbol(PredicateDefinition predicateDefinition) {
204 int arity = predicateDefinition.getParameters().size();
205 if (predicateDefinition.isError()) {
206 collectPartialRelation(predicateDefinition, arity, TruthValue.FALSE, TruthValue.FALSE);
207 } else {
208 collectPartialRelation(predicateDefinition, arity, null, TruthValue.UNKNOWN);
209 }
210 }
211
212 private void putRelationInfo(Relation relation, RelationInfo info) {
213 relationInfoMap.put(relation, info);
214 partialRelationInfoMap.put(info.partialRelation(), info);
215 inverseTrace.put(info.partialRelation(), relation);
216 }
217
218 private RelationInfo collectPartialRelation(Relation relation, int arity, TruthValue value,
219 TruthValue defaultValue) {
220 return relationInfoMap.computeIfAbsent(relation, key -> {
221 var name = getName(relation);
222 var info = new RelationInfo(name, arity, value, defaultValue);
223 partialRelationInfoMap.put(info.partialRelation(), info);
224 inverseTrace.put(info.partialRelation(), relation);
225 return info;
226 });
227 }
228
229 private String getName(Relation relation) {
230 return semanticsUtils.getName(relation).orElseGet(() -> "#" + relationInfoMap.size());
231 }
232
233 private void collectMetamodel() {
234 for (var statement : problem.getStatements()) {
235 if (statement instanceof ClassDeclaration classDeclaration) {
236 collectClassDeclarationMetamodel(classDeclaration);
237 } else if (statement instanceof EnumDeclaration enumDeclaration) {
238 collectEnumMetamodel(enumDeclaration);
63 } 239 }
64 } 240 }
65 defaultAssertions.overwriteValues(assertions); 241 }
66 if (isEqualsRelation) { 242
67 for (int i = 0; i < nodeCount; i++) { 243 private void collectEnumMetamodel(EnumDeclaration enumDeclaration) {
68 defaultAssertions.setIfMissing(Tuple.of(i, i), TruthValue.TRUE); 244 try {
245 metamodelBuilder.type(getPartialRelation(enumDeclaration), nodeRelation);
246 } catch (RuntimeException e) {
247 throw TracedException.addTrace(enumDeclaration, e);
248 }
249 }
250
251 private void collectClassDeclarationMetamodel(ClassDeclaration classDeclaration) {
252 var superTypes = classDeclaration.getSuperTypes();
253 var partialSuperTypes = new ArrayList<PartialRelation>(superTypes.size() + 1);
254 partialSuperTypes.add(nodeRelation);
255 for (var superType : superTypes) {
256 partialSuperTypes.add(getPartialRelation(superType));
257 }
258 try {
259 metamodelBuilder.type(getPartialRelation(classDeclaration), classDeclaration.isAbstract(),
260 partialSuperTypes);
261 } catch (RuntimeException e) {
262 throw TracedException.addTrace(classDeclaration, e);
263 }
264 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
265 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
266 collectReferenceDeclarationMetamodel(classDeclaration, referenceDeclaration);
69 } 267 }
70 defaultAssertions.setAllMissing(TruthValue.FALSE);
71 } 268 }
72 return defaultAssertions; 269 }
270
271 private void collectReferenceDeclarationMetamodel(ClassDeclaration classDeclaration,
272 ReferenceDeclaration referenceDeclaration) {
273 var relation = getPartialRelation(referenceDeclaration);
274 var source = getPartialRelation(classDeclaration);
275 var target = getPartialRelation(referenceDeclaration.getReferenceType());
276 boolean containment = referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT;
277 var opposite = referenceDeclaration.getOpposite();
278 PartialRelation oppositeRelation = null;
279 if (opposite != null) {
280 oppositeRelation = getPartialRelation(opposite);
281 }
282 var multiplicity = getMultiplicityConstraint(referenceDeclaration);
283 try {
284 metamodelBuilder.reference(relation, source, containment, multiplicity, target, oppositeRelation);
285 } catch (RuntimeException e) {
286 throw TracedException.addTrace(classDeclaration, e);
287 }
288 }
289
290 private Multiplicity getMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) {
291 if (!ProblemUtil.hasMultiplicityConstraint(referenceDeclaration)) {
292 return UnconstrainedMultiplicity.INSTANCE;
293 }
294 var problemMultiplicity = referenceDeclaration.getMultiplicity();
295 CardinalityInterval interval;
296 if (problemMultiplicity == null) {
297 interval = CardinalityIntervals.LONE;
298 } else {
299 interval = getCardinalityInterval(problemMultiplicity);
300 }
301 var constraint = getRelationInfo(referenceDeclaration.getInvalidMultiplicity()).partialRelation();
302 return ConstrainedMultiplicity.of(interval, constraint);
303 }
304
305 private static CardinalityInterval getCardinalityInterval(
306 tools.refinery.language.model.problem.Multiplicity problemMultiplicity) {
307 if (problemMultiplicity instanceof ExactMultiplicity exactMultiplicity) {
308 return CardinalityIntervals.exactly(exactMultiplicity.getExactValue());
309 } else if (problemMultiplicity instanceof RangeMultiplicity rangeMultiplicity) {
310 var upperBound = rangeMultiplicity.getUpperBound();
311 return CardinalityIntervals.between(rangeMultiplicity.getLowerBound(),
312 upperBound < 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(upperBound));
313 } else {
314 throw new TracedException(problemMultiplicity, "Unknown multiplicity");
315 }
316 }
317
318 private void collectAssertions() {
319 for (var statement : problem.getStatements()) {
320 if (statement instanceof ClassDeclaration classDeclaration) {
321 collectClassDeclarationAssertions(classDeclaration);
322 } else if (statement instanceof EnumDeclaration enumDeclaration) {
323 collectEnumAssertions(enumDeclaration);
324 } else if (statement instanceof IndividualDeclaration individualDeclaration) {
325 for (var individual : individualDeclaration.getNodes()) {
326 collectIndividualAssertions(individual);
327 }
328 } else if (statement instanceof Assertion assertion) {
329 collectAssertion(assertion);
330 }
331 }
332 }
333
334 private void collectClassDeclarationAssertions(ClassDeclaration classDeclaration) {
335 var newNode = classDeclaration.getNewNode();
336 if (newNode == null) {
337 return;
338 }
339 var newNodeId = getNodeId(newNode);
340 collectCardinalityAssertions(newNodeId, TruthValue.UNKNOWN);
341 var tuple = Tuple.of(newNodeId);
342 mergeValue(classDeclaration, tuple, TruthValue.TRUE);
343 var typeInfo = metamodel.typeHierarchy().getAnalysisResult(getPartialRelation(classDeclaration));
344 for (var subType : typeInfo.getDirectSubtypes()) {
345 partialRelationInfoMap.get(subType).assertions().mergeValue(tuple, TruthValue.FALSE);
346 }
347 }
348
349 private void collectEnumAssertions(EnumDeclaration enumDeclaration) {
350 var overlay = MutableSeed.of(1, null);
351 for (var literal : enumDeclaration.getLiterals()) {
352 collectIndividualAssertions(literal);
353 var nodeId = getNodeId(literal);
354 overlay.mergeValue(Tuple.of(nodeId), TruthValue.TRUE);
355 }
356 var info = getRelationInfo(enumDeclaration);
357 info.assertions().overwriteValues(overlay);
358 }
359
360 private void collectIndividualAssertions(Node node) {
361 var nodeId = getNodeId(node);
362 collectCardinalityAssertions(nodeId, TruthValue.TRUE);
363 }
364
365 private void collectCardinalityAssertions(int nodeId, TruthValue value) {
366 mergeValue(builtinSymbols.exists(), Tuple.of(nodeId), value);
367 mergeValue(builtinSymbols.equals(), Tuple.of(nodeId, nodeId), value);
368 }
369
370 private void collectAssertion(Assertion assertion) {
371 var tuple = getTuple(assertion);
372 var value = getTruthValue(assertion.getValue());
373 var relation = assertion.getRelation();
374 var info = getRelationInfo(relation);
375 var partialRelation = info.partialRelation();
376 if (partialRelation.arity() != tuple.getSize()) {
377 throw new TracedException(assertion, "Expected %d arguments for %s, got %d instead"
378 .formatted(partialRelation.arity(), partialRelation, tuple.getSize()));
379 }
380 if (assertion.isDefault()) {
381 info.defaultAssertions().mergeValue(tuple, value);
382 } else {
383 info.assertions().mergeValue(tuple, value);
384 }
385 }
386
387 private void mergeValue(Relation relation, Tuple key, TruthValue value) {
388 getRelationInfo(relation).assertions().mergeValue(key, value);
389 }
390
391 private RelationInfo getRelationInfo(Relation relation) {
392 var info = relationInfoMap.get(relation);
393 if (info == null) {
394 throw new IllegalArgumentException("Unknown relation: " + relation);
395 }
396 return info;
397 }
398
399 private PartialRelation getPartialRelation(Relation relation) {
400 return getRelationInfo(relation).partialRelation();
401 }
402
403 private int getNodeId(Node node) {
404 return nodeTrace.getOrThrow(node);
73 } 405 }
74 406
75 private Tuple getTuple(Assertion assertion) { 407 private Tuple getTuple(Assertion assertion) {
@@ -79,11 +411,11 @@ public class ModelInitializer {
79 for (int i = 0; i < arity; i++) { 411 for (int i = 0; i < arity; i++) {
80 var argument = arguments.get(i); 412 var argument = arguments.get(i);
81 if (argument instanceof NodeAssertionArgument nodeArgument) { 413 if (argument instanceof NodeAssertionArgument nodeArgument) {
82 nodes[i] = nodeTrace.getOrThrow(nodeArgument.getNode()); 414 nodes[i] = getNodeId(nodeArgument.getNode());
83 } else if (argument instanceof WildcardAssertionArgument) { 415 } else if (argument instanceof WildcardAssertionArgument) {
84 nodes[i] = -1; 416 nodes[i] = -1;
85 } else { 417 } else {
86 throw new IllegalArgumentException("Unknown assertion argument: " + argument); 418 throw new TracedException(argument, "Unsupported assertion argument");
87 } 419 }
88 } 420 }
89 return Tuple.of(nodes); 421 return Tuple.of(nodes);
@@ -100,4 +432,249 @@ public class ModelInitializer {
100 case ERROR -> TruthValue.ERROR; 432 case ERROR -> TruthValue.ERROR;
101 }; 433 };
102 } 434 }
435
436 private void collectPredicates() {
437 for (var statement : problem.getStatements()) {
438 if (statement instanceof PredicateDefinition predicateDefinition) {
439 collectPredicateDefinitionTraced(predicateDefinition);
440 }
441 }
442 }
443
444 private void collectPredicateDefinitionTraced(PredicateDefinition predicateDefinition) {
445 try {
446 collectPredicateDefinition(predicateDefinition);
447 } catch (InvalidClauseException e) {
448 int clauseIndex = e.getClauseIndex();
449 var bodies = predicateDefinition.getBodies();
450 if (clauseIndex < bodies.size()) {
451 throw new TracedException(bodies.get(clauseIndex), e);
452 } else {
453 throw new TracedException(predicateDefinition, e);
454 }
455 } catch (RuntimeException e) {
456 throw TracedException.addTrace(predicateDefinition, e);
457 }
458 }
459
460 private void collectPredicateDefinition(PredicateDefinition predicateDefinition) {
461 var partialRelation = getPartialRelation(predicateDefinition);
462 var query = toQuery(partialRelation.name(), predicateDefinition);
463 boolean mutable;
464 TruthValue defaultValue;
465 if (predicateDefinition.isError()) {
466 mutable = false;
467 defaultValue = TruthValue.FALSE;
468 } else {
469 var seed = modelSeed.getSeed(partialRelation);
470 defaultValue = seed.reducedValue() == TruthValue.FALSE ? TruthValue.FALSE : TruthValue.UNKNOWN;
471 var cursor = seed.getCursor(defaultValue, getNodeCount());
472 // The symbol should be mutable if there is at least one non-default entry in the seed.
473 mutable = cursor.move();
474 }
475 var translator = new PredicateTranslator(partialRelation, query, mutable, defaultValue);
476 storeBuilder.with(translator);
477 }
478
479 private RelationalQuery toQuery(String name, PredicateDefinition predicateDefinition) {
480 var problemParameters = predicateDefinition.getParameters();
481 int arity = problemParameters.size();
482 var parameters = new NodeVariable[arity];
483 var parameterMap = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(arity);
484 var commonLiterals = new ArrayList<Literal>();
485 for (int i = 0; i < arity; i++) {
486 var problemParameter = problemParameters.get(i);
487 var parameter = Variable.of(problemParameter.getName());
488 parameters[i] = parameter;
489 parameterMap.put(problemParameter, parameter);
490 var parameterType = problemParameter.getParameterType();
491 if (parameterType != null) {
492 var partialType = getPartialRelation(parameterType);
493 commonLiterals.add(partialType.call(parameter));
494 }
495 }
496 var builder = Query.builder(name).parameters(parameters);
497 for (var body : predicateDefinition.getBodies()) {
498 try {
499 var localScope = extendScope(parameterMap, body.getImplicitVariables());
500 var problemLiterals = body.getLiterals();
501 var literals = new ArrayList<>(commonLiterals);
502 for (var problemLiteral : problemLiterals) {
503 toLiteralsTraced(problemLiteral, localScope, literals);
504 }
505 builder.clause(literals);
506 } catch (RuntimeException e) {
507 throw TracedException.addTrace(body, e);
508 }
509 }
510 return builder.build();
511 }
512
513 private Map<tools.refinery.language.model.problem.Variable, Variable> extendScope(
514 Map<tools.refinery.language.model.problem.Variable, Variable> existing,
515 Collection<? extends tools.refinery.language.model.problem.Variable> newVariables) {
516 if (newVariables.isEmpty()) {
517 return existing;
518 }
519 int localScopeSize = existing.size() + newVariables.size();
520 var localScope = new HashMap<tools.refinery.language.model.problem.Variable, Variable>(localScopeSize);
521 localScope.putAll(existing);
522 for (var newVariable : newVariables) {
523 localScope.put(newVariable, Variable.of(newVariable.getName()));
524 }
525 return localScope;
526 }
527
528 private void toLiteralsTraced(Expr expr, Map<tools.refinery.language.model.problem.Variable, Variable> localScope,
529 List<Literal> literals) {
530 try {
531 toLiterals(expr, localScope, literals);
532 } catch (RuntimeException e) {
533 throw TracedException.addTrace(expr, e);
534 }
535 }
536
537 private void toLiterals(Expr expr, Map<tools.refinery.language.model.problem.Variable,
538 Variable> localScope,
539 List<Literal> literals) {
540 if (expr instanceof LogicConstant logicConstant) {
541 switch (logicConstant.getLogicValue()) {
542 case TRUE -> literals.add(BooleanLiteral.TRUE);
543 case FALSE -> literals.add(BooleanLiteral.FALSE);
544 default -> throw new TracedException(logicConstant, "Unsupported literal");
545 }
546 } else if (expr instanceof Atom atom) {
547 var target = getPartialRelation(atom.getRelation());
548 var polarity = atom.isTransitiveClosure() ? CallPolarity.TRANSITIVE : CallPolarity.POSITIVE;
549 var argumentList = toArgumentList(atom.getArguments(), localScope, literals);
550 literals.add(target.call(polarity, argumentList));
551 } else if (expr instanceof NegationExpr negationExpr) {
552 var body = negationExpr.getBody();
553 if (!(body instanceof Atom atom)) {
554 throw new TracedException(body, "Cannot negate literal");
555 }
556 var target = getPartialRelation(atom.getRelation());
557 Constraint constraint;
558 if (atom.isTransitiveClosure()) {
559 constraint = Query.of(target.name() + "#transitive", (builder, p1, p2) -> builder.clause(
560 target.callTransitive(p1, p2)
561 )).getDnf();
562 } else {
563 constraint = target;
564 }
565 var negatedScope = extendScope(localScope, negationExpr.getImplicitVariables());
566 var argumentList = toArgumentList(atom.getArguments(), negatedScope, literals);
567 literals.add(constraint.call(CallPolarity.NEGATIVE, argumentList));
568 } else if (expr instanceof ComparisonExpr comparisonExpr) {
569 var argumentList = toArgumentList(List.of(comparisonExpr.getLeft(), comparisonExpr.getRight()),
570 localScope, literals);
571 boolean positive = switch (comparisonExpr.getOp()) {
572 case EQ -> true;
573 case NOT_EQ -> false;
574 default -> throw new TracedException(
575 comparisonExpr, "Unsupported operator");
576 };
577 literals.add(new EquivalenceLiteral(positive, argumentList.get(0), argumentList.get(1)));
578 } else {
579 throw new TracedException(expr, "Unsupported literal");
580 }
581 }
582
583 private List<Variable> toArgumentList(
584 List<Expr> expressions, Map<tools.refinery.language.model.problem.Variable, Variable> localScope,
585 List<Literal> literals) {
586 var argumentList = new ArrayList<Variable>(expressions.size());
587 for (var expr : expressions) {
588 if (!(expr instanceof VariableOrNodeExpr variableOrNodeExpr)) {
589 throw new TracedException(expr, "Unsupported argument");
590 }
591 var variableOrNode = variableOrNodeExpr.getVariableOrNode();
592 if (variableOrNode instanceof Node node) {
593 int nodeId = getNodeId(node);
594 var tempVariable = Variable.of(semanticsUtils.getName(node).orElse("_" + nodeId));
595 literals.add(new ConstantLiteral(tempVariable, nodeId));
596 argumentList.add(tempVariable);
597 } else if (variableOrNode instanceof tools.refinery.language.model.problem.Variable problemVariable) {
598 if (variableOrNodeExpr.getSingletonVariable() == problemVariable) {
599 argumentList.add(Variable.of(problemVariable.getName()));
600 } else {
601 var variable = localScope.get(problemVariable);
602 if (variable == null) {
603 throw new TracedException(variableOrNode, "Unknown variable: " + problemVariable.getName());
604 }
605 argumentList.add(variable);
606 }
607 } else {
608 throw new TracedException(variableOrNode, "Unknown argument");
609 }
610 }
611 return argumentList;
612 }
613
614 private void collectScopes() {
615 for (var statement : problem.getStatements()) {
616 if (statement instanceof ScopeDeclaration scopeDeclaration) {
617 for (var typeScope : scopeDeclaration.getTypeScopes()) {
618 if (typeScope.isIncrement()) {
619 collectTypeScopeIncrement(typeScope);
620 } else {
621 collectTypeScope(typeScope);
622 }
623 }
624 }
625 }
626 }
627
628 private void collectTypeScopeIncrement(TypeScope typeScope) {
629 if (!(typeScope.getTargetType() instanceof ClassDeclaration classDeclaration)) {
630 throw new TracedException(typeScope, "Target of incremental type scope must be a class declaration");
631 }
632 var newNode = classDeclaration.getNewNode();
633 if (newNode == null) {
634 throw new TracedException(typeScope, "Target of incremental type scope must be concrete class");
635 }
636 int newNodeId = nodeTrace.get(newNode);
637 var type = relationTrace.get(classDeclaration);
638 var typeInfo = metamodel.typeHierarchy().getAnalysisResult(type);
639 if (!typeInfo.getDirectSubtypes().isEmpty()) {
640 throw new TracedException(typeScope, "Target of incremental type scope cannot have any subclasses");
641 }
642 var interval = getCardinalityInterval(typeScope.getMultiplicity());
643 countSeed.compute(Tuple.of(newNodeId), (key, oldValue) ->
644 oldValue == null ? interval : oldValue.meet(interval));
645 }
646
647 private void collectTypeScope(TypeScope typeScope) {
648 var scopePropagatorBuilder = storeBuilder.tryGetAdapter(ScopePropagatorBuilder.class).orElseThrow(
649 () -> new TracedException(typeScope, "Type scopes require a ScopePropagatorBuilder"));
650 var type = relationTrace.get(typeScope.getTargetType());
651 if (type == null) {
652 throw new TracedException(typeScope, "Unknown target type");
653 }
654 var interval = getCardinalityInterval(typeScope.getMultiplicity());
655 scopePropagatorBuilder.scope(type, interval);
656 }
657
658 private record RelationInfo(PartialRelation partialRelation, MutableSeed<TruthValue> assertions,
659 MutableSeed<TruthValue> defaultAssertions) {
660 public RelationInfo(String name, int arity, TruthValue value, TruthValue defaultValue) {
661 this(new PartialRelation(name, arity), value, defaultValue);
662 }
663
664 public RelationInfo(PartialRelation partialRelation, TruthValue value, TruthValue defaultValue) {
665 this(partialRelation, MutableSeed.of(partialRelation.arity(), value),
666 MutableSeed.of(partialRelation.arity(), defaultValue));
667 }
668
669 public Seed<TruthValue> toSeed(int nodeCount) {
670 defaultAssertions.overwriteValues(assertions);
671 if (partialRelation.equals(ReasoningAdapter.EQUALS_SYMBOL)) {
672 for (int i = 0; i < nodeCount; i++) {
673 defaultAssertions.setIfMissing(Tuple.of(i, i), TruthValue.TRUE);
674 }
675 defaultAssertions.setAllMissing(TruthValue.FALSE);
676 }
677 return defaultAssertions;
678 }
679 }
103} 680}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java
new file mode 100644
index 00000000..47c89e9b
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/SemanticsUtils.java
@@ -0,0 +1,31 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model;
7
8import com.google.inject.Inject;
9import com.google.inject.Singleton;
10import org.eclipse.emf.ecore.EObject;
11import org.eclipse.xtext.naming.IQualifiedNameConverter;
12import org.eclipse.xtext.naming.IQualifiedNameProvider;
13
14import java.util.Optional;
15
16@Singleton
17public class SemanticsUtils {
18 @Inject
19 private IQualifiedNameProvider qualifiedNameProvider;
20
21 @Inject
22 private IQualifiedNameConverter qualifiedNameConverter;
23
24 public Optional<String> getName(EObject eObject) {
25 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(eObject);
26 if (qualifiedName == null) {
27 return Optional.empty();
28 }
29 return Optional.of(qualifiedNameConverter.toString(qualifiedName));
30 }
31}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java
new file mode 100644
index 00000000..38fd8a67
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/TracedException.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model;
7
8import org.eclipse.emf.ecore.EObject;
9
10public class TracedException extends RuntimeException {
11 private final transient EObject sourceElement;
12
13 public TracedException(EObject sourceElement) {
14 this.sourceElement = sourceElement;
15 }
16
17 public TracedException(EObject sourceElement, String message) {
18 super(message);
19 this.sourceElement = sourceElement;
20 }
21
22 public TracedException(EObject sourceElement, String message, Throwable cause) {
23 super(message, cause);
24 this.sourceElement = sourceElement;
25 }
26
27 public TracedException(EObject sourceElement, Throwable cause) {
28 super(cause);
29 this.sourceElement = sourceElement;
30 }
31
32 public EObject getSourceElement() {
33 return sourceElement;
34 }
35
36 @Override
37 public String getMessage() {
38 var message = super.getMessage();
39 if (message == null) {
40 return "Internal error";
41 }
42 return message;
43 }
44
45 public static TracedException addTrace(EObject sourceElement, Throwable cause) {
46 if (cause instanceof TracedException tracedException && tracedException.sourceElement != null) {
47 return tracedException;
48 }
49 return new TracedException(sourceElement, cause);
50 }
51}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
index c1afecf9..32112e61 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTree.java
@@ -7,10 +7,10 @@ package tools.refinery.language.semantics.model.internal;
7 7
8import org.eclipse.collections.api.factory.primitive.IntObjectMaps; 8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.representation.TruthValue; 10import tools.refinery.store.representation.TruthValue;
11import tools.refinery.store.tuple.Tuple;
12 12
13public class DecisionTree { 13class DecisionTree implements MutableSeed<TruthValue> {
14 private final int levels; 14 private final int levels;
15 15
16 private final DecisionTreeNode root; 16 private final DecisionTreeNode root;
@@ -29,30 +29,53 @@ public class DecisionTree {
29 this(levels, null); 29 this(levels, null);
30 } 30 }
31 31
32 @Override
33 public int arity() {
34 return levels;
35 }
36
37 @Override
38 public Class<TruthValue> valueType() {
39 return TruthValue.class;
40 }
41
42 @Override
43 public TruthValue reducedValue() {
44 return root.getOtherwiseReducedValue().getTruthValue();
45 }
46
47 @Override
32 public TruthValue get(Tuple tuple) { 48 public TruthValue get(Tuple tuple) {
33 return root.getValue(levels - 1, tuple).getTruthValue(); 49 return root.getValue(levels - 1, tuple).getTruthValue();
34 } 50 }
35 51
52 @Override
36 public void mergeValue(Tuple tuple, TruthValue truthValue) { 53 public void mergeValue(Tuple tuple, TruthValue truthValue) {
37 if (truthValue != null) { 54 if (truthValue != null) {
38 root.mergeValue(levels - 1, tuple, truthValue); 55 root.mergeValue(levels - 1, tuple, truthValue);
39 } 56 }
40 } 57 }
41 58
59 @Override
42 public void setIfMissing(Tuple tuple, TruthValue truthValue) { 60 public void setIfMissing(Tuple tuple, TruthValue truthValue) {
43 if (truthValue != null) { 61 if (truthValue != null) {
44 root.setIfMissing(levels - 1, tuple, truthValue); 62 root.setIfMissing(levels - 1, tuple, truthValue);
45 } 63 }
46 } 64 }
47 65
66 @Override
48 public void setAllMissing(TruthValue truthValue) { 67 public void setAllMissing(TruthValue truthValue) {
49 if (truthValue != null) { 68 if (truthValue != null) {
50 root.setAllMissing(truthValue); 69 root.setAllMissing(truthValue);
51 } 70 }
52 } 71 }
53 72
54 public void overwriteValues(DecisionTree values) { 73 @Override
55 root.overwriteValues(values.root); 74 public void overwriteValues(MutableSeed<TruthValue> values) {
75 if (!(values instanceof DecisionTree decisionTree)) {
76 throw new IllegalArgumentException("Incompatible overwrite: " + values);
77 }
78 root.overwriteValues(decisionTree.root);
56 } 79 }
57 80
58 public TruthValue getReducedValue() { 81 public TruthValue getReducedValue() {
@@ -60,6 +83,7 @@ public class DecisionTree {
60 return reducedValue == null ? null : reducedValue.getTruthValue(); 83 return reducedValue == null ? null : reducedValue.getTruthValue();
61 } 84 }
62 85
86 @Override
63 public Cursor<Tuple, TruthValue> getCursor(TruthValue defaultValue, int nodeCount) { 87 public Cursor<Tuple, TruthValue> getCursor(TruthValue defaultValue, int nodeCount) {
64 return new DecisionTreeCursor(levels, defaultValue, nodeCount, root); 88 return new DecisionTreeCursor(levels, defaultValue, nodeCount, root);
65 } 89 }
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
index 9a1e15a3..a9fc644a 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/DecisionTreeCursor.java
@@ -67,6 +67,15 @@ class DecisionTreeCursor implements Cursor<Tuple, TruthValue> {
67 67
68 @Override 68 @Override
69 public boolean move() { 69 public boolean move() {
70 while (moveOne()) {
71 if (!value.equals(defaultValue)) {
72 return true;
73 }
74 }
75 return false;
76 }
77
78 private boolean moveOne() {
70 boolean found = false; 79 boolean found = false;
71 if (path.isEmpty() && !terminated) { 80 if (path.isEmpty() && !terminated) {
72 found = root.moveNext(levels - 1, this); 81 found = root.moveNext(levels - 1, this);
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/MutableSeed.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/MutableSeed.java
new file mode 100644
index 00000000..99019e2a
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/MutableSeed.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model.internal;
7
8import tools.refinery.store.reasoning.seed.Seed;
9import tools.refinery.store.representation.TruthValue;
10import tools.refinery.store.tuple.Tuple;
11
12public interface MutableSeed<T> extends Seed<T> {
13 void mergeValue(Tuple tuple, T value);
14
15 void setIfMissing(Tuple tuple, T value);
16
17 void setAllMissing(T value);
18
19 void overwriteValues(MutableSeed<T> other);
20
21 static MutableSeed<TruthValue> of(int levels, TruthValue initialValue) {
22 if (levels == 0) {
23 return new NullaryMutableSeed(initialValue);
24 } else {
25 return new DecisionTree(levels, initialValue);
26 }
27 }
28}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/NullaryMutableSeed.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/NullaryMutableSeed.java
new file mode 100644
index 00000000..80644b1f
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/model/internal/NullaryMutableSeed.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics.model.internal;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.representation.TruthValue;
11import tools.refinery.store.tuple.Tuple;
12
13class NullaryMutableSeed implements MutableSeed<TruthValue> {
14 private DecisionTreeValue value;
15
16 public NullaryMutableSeed(TruthValue reducedValue) {
17
18 value = DecisionTreeValue.fromTruthValue(reducedValue);
19 }
20
21 @Override
22 public int arity() {
23 return 0;
24 }
25
26 @Override
27 public Class<TruthValue> valueType() {
28 return TruthValue.class;
29 }
30
31 @Override
32 public TruthValue reducedValue() {
33 return value.getTruthValue();
34 }
35
36 @Override
37 public TruthValue get(Tuple key) {
38 validateKey(key);
39 return reducedValue();
40 }
41
42 private static void validateKey(Tuple key) {
43 if (key.getSize() > 0) {
44 throw new IllegalArgumentException("Invalid key: " + key);
45 }
46 }
47
48 @Override
49 public Cursor<Tuple, TruthValue> getCursor(TruthValue defaultValue, int nodeCount) {
50 if (value == DecisionTreeValue.UNSET || value.getTruthValue() == defaultValue) {
51 return Cursors.empty();
52 }
53 return Cursors.singleton(Tuple.of(), value.getTruthValue());
54 }
55
56 @Override
57 public void mergeValue(Tuple tuple, TruthValue value) {
58 this.value = DecisionTreeValue.fromTruthValue(this.value.merge(value));
59 }
60
61 @Override
62 public void setIfMissing(Tuple tuple, TruthValue value) {
63 validateKey(tuple);
64 setAllMissing(value);
65 }
66
67 @Override
68 public void setAllMissing(TruthValue value) {
69 if (this.value == DecisionTreeValue.UNSET) {
70 this.value = DecisionTreeValue.fromTruthValue(value);
71 }
72 }
73
74 @Override
75 public void overwriteValues(MutableSeed<TruthValue> other) {
76 if (!(other instanceof NullaryMutableSeed nullaryMutableSeed)) {
77 throw new IllegalArgumentException("Incompatible overwrite: " + other);
78 }
79 if (nullaryMutableSeed.value != DecisionTreeValue.UNSET) {
80 value = nullaryMutableSeed.value;
81 }
82 }
83}
diff --git a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/internal/DecisionTreeTests.java
index b3fcbabb..5d039308 100644
--- a/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/tests/DecisionTreeTests.java
+++ b/subprojects/language-semantics/src/test/java/tools/refinery/language/semantics/model/internal/DecisionTreeTests.java
@@ -3,10 +3,9 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.semantics.model.tests; 6package tools.refinery.language.semantics.model.internal;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.language.semantics.model.internal.DecisionTree;
10import tools.refinery.store.representation.TruthValue; 9import tools.refinery.store.representation.TruthValue;
11import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
12 11
@@ -134,6 +133,17 @@ class DecisionTreeTests {
134 } 133 }
135 134
136 @Test 135 @Test
136 void overwriteIterationTest() {
137 var sut = new DecisionTree(1, TruthValue.TRUE);
138 var overwrite = new DecisionTree(1, null);
139 overwrite.mergeValue(Tuple.of(0), TruthValue.UNKNOWN);
140 sut.overwriteValues(overwrite);
141 var map = iterateAll(sut, TruthValue.UNKNOWN, 2);
142 assertThat(map.keySet(), hasSize(1));
143 assertThat(map, hasEntry(Tuple.of(1), TruthValue.TRUE));
144 }
145
146 @Test
137 void overwriteNothingTest() { 147 void overwriteNothingTest() {
138 var sut = new DecisionTree(2, TruthValue.UNKNOWN); 148 var sut = new DecisionTree(2, TruthValue.UNKNOWN);
139 var values = new DecisionTree(2, null); 149 var values = new DecisionTree(2, null);
diff --git a/subprojects/language-web/build.gradle.kts b/subprojects/language-web/build.gradle.kts
index 562a1bd9..88dccdf3 100644
--- a/subprojects/language-web/build.gradle.kts
+++ b/subprojects/language-web/build.gradle.kts
@@ -17,6 +17,10 @@ val webapp: Configuration by configurations.creating {
17dependencies { 17dependencies {
18 implementation(project(":refinery-language")) 18 implementation(project(":refinery-language"))
19 implementation(project(":refinery-language-ide")) 19 implementation(project(":refinery-language-ide"))
20 implementation(project(":refinery-language-semantics"))
21 implementation(project(":refinery-store-query-viatra"))
22 implementation(project(":refinery-store-reasoning-scope"))
23 implementation(libs.gson)
20 implementation(libs.jetty.server) 24 implementation(libs.jetty.server)
21 implementation(libs.jetty.servlet) 25 implementation(libs.jetty.servlet)
22 implementation(libs.jetty.websocket.api) 26 implementation(libs.jetty.websocket.api)
@@ -60,9 +64,18 @@ tasks {
60 classpath(mainRuntimeClasspath) 64 classpath(mainRuntimeClasspath)
61 mainClass.set(application.mainClass) 65 mainClass.set(application.mainClass)
62 standardInput = System.`in` 66 standardInput = System.`in`
63 val baseResource = webapp.incoming.artifacts.artifactFiles.first() 67 environment("REFINERY_BASE_RESOURCE", webapp.singleFile)
64 environment("BASE_RESOURCE", baseResource)
65 group = "run" 68 group = "run"
66 description = "Start a Jetty web server serving the Xtex API and assets." 69 description = "Start a Jetty web server serving the Xtext API and assets."
70 }
71
72 register<JavaExec>("serveBackendOnly") {
73 val mainRuntimeClasspath = sourceSets.main.map { it.runtimeClasspath }
74 dependsOn(mainRuntimeClasspath)
75 classpath(mainRuntimeClasspath)
76 mainClass.set(application.mainClass)
77 standardInput = System.`in`
78 group = "run"
79 description = "Start a Jetty web server serving the Xtext API without assets."
67 } 80 }
68} 81}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
index b0197c01..6a6e0107 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java
@@ -9,11 +9,13 @@
9 */ 9 */
10package tools.refinery.language.web; 10package tools.refinery.language.web;
11 11
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
12import org.eclipse.xtext.web.server.XtextServiceDispatcher; 13import org.eclipse.xtext.web.server.XtextServiceDispatcher;
13import org.eclipse.xtext.web.server.model.IWebDocumentProvider; 14import org.eclipse.xtext.web.server.model.IWebDocumentProvider;
14import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; 15import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
15import org.eclipse.xtext.web.server.occurrences.OccurrencesService; 16import org.eclipse.xtext.web.server.occurrences.OccurrencesService;
16import tools.refinery.language.web.occurrences.ProblemOccurrencesService; 17import tools.refinery.language.web.occurrences.ProblemOccurrencesService;
18import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider;
17import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; 19import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher;
18import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; 20import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess;
19import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; 21import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider;
@@ -37,4 +39,8 @@ public class ProblemWebModule extends AbstractProblemWebModule {
37 public Class<? extends OccurrencesService> bindOccurrencesService() { 39 public Class<? extends OccurrencesService> bindOccurrencesService() {
38 return ProblemOccurrencesService.class; 40 return ProblemOccurrencesService.class;
39 } 41 }
42
43 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() {
44 return ThreadPoolExecutorServiceProvider.class;
45 }
40} 46}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java
index 7b48cde8..e98d115e 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java
@@ -10,8 +10,10 @@ import org.eclipse.xtext.util.DisposableRegistry;
10import jakarta.servlet.ServletException; 10import jakarta.servlet.ServletException;
11import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; 11import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet;
12 12
13public class ProblemWebSocketServlet extends XtextWebSocketServlet { 13import java.io.Serial;
14 14
15public class ProblemWebSocketServlet extends XtextWebSocketServlet {
16 @Serial
15 private static final long serialVersionUID = -7040955470384797008L; 17 private static final long serialVersionUID = -7040955470384797008L;
16 18
17 private transient DisposableRegistry disposableRegistry; 19 private transient DisposableRegistry disposableRegistry;
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java
index 7b094fde..fab94689 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/SecurityHeadersFilter.java
@@ -16,7 +16,7 @@ public class SecurityHeadersFilter implements Filter {
16 ServletException { 16 ServletException {
17 if (response instanceof HttpServletResponse httpResponse) { 17 if (response instanceof HttpServletResponse httpResponse) {
18 httpResponse.setHeader("Content-Security-Policy", "default-src 'none'; " + 18 httpResponse.setHeader("Content-Security-Policy", "default-src 'none'; " +
19 "script-src 'self'; " + 19 "script-src 'self' 'wasm-unsafe-eval'; " +
20 // CodeMirror needs inline styles, see e.g., 20 // CodeMirror needs inline styles, see e.g.,
21 // https://discuss.codemirror.net/t/inline-styles-and-content-security-policy/1311/2 21 // https://discuss.codemirror.net/t/inline-styles-and-content-security-policy/1311/2
22 "style-src 'self' 'unsafe-inline'; " + 22 "style-src 'self' 'unsafe-inline'; " +
@@ -25,7 +25,7 @@ public class SecurityHeadersFilter implements Filter {
25 "font-src 'self'; " + 25 "font-src 'self'; " +
26 "connect-src 'self'; " + 26 "connect-src 'self'; " +
27 "manifest-src 'self'; " + 27 "manifest-src 'self'; " +
28 "worker-src 'self';"); 28 "worker-src 'self' blob:;");
29 httpResponse.setHeader("X-Content-Type-Options", "nosniff"); 29 httpResponse.setHeader("X-Content-Type-Options", "nosniff");
30 httpResponse.setHeader("X-Frame-Options", "DENY"); 30 httpResponse.setHeader("X-Frame-Options", "DENY");
31 httpResponse.setHeader("Referrer-Policy", "strict-origin"); 31 httpResponse.setHeader("Referrer-Policy", "strict-origin");
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
index ad19e77d..155efc6f 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
@@ -33,7 +33,7 @@ import java.util.EnumSet;
33import java.util.Set; 33import java.util.Set;
34 34
35public class ServerLauncher { 35public class ServerLauncher {
36 public static final String DEFAULT_LISTEN_ADDRESS = "localhost"; 36 public static final String DEFAULT_LISTEN_HOST = "localhost";
37 37
38 public static final int DEFAULT_LISTEN_PORT = 1312; 38 public static final int DEFAULT_LISTEN_PORT = 1312;
39 39
@@ -105,7 +105,7 @@ public class ServerLauncher {
105 105
106 private Resource getBaseResource() { 106 private Resource getBaseResource() {
107 var factory = ResourceFactory.of(server); 107 var factory = ResourceFactory.of(server);
108 var baseResourceOverride = System.getenv("BASE_RESOURCE"); 108 var baseResourceOverride = System.getenv("REFINERY_BASE_RESOURCE");
109 if (baseResourceOverride != null) { 109 if (baseResourceOverride != null) {
110 // If a user override is provided, use it. 110 // If a user override is provided, use it.
111 return factory.newResource(baseResourceOverride); 111 return factory.newResource(baseResourceOverride);
@@ -115,7 +115,10 @@ public class ServerLauncher {
115 // If the app is packaged in the jar, serve it. 115 // If the app is packaged in the jar, serve it.
116 URI webRootUri; 116 URI webRootUri;
117 try { 117 try {
118 webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); 118 webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString()
119 .replaceFirst("/index.html$", "/")
120 // Enable running without warnings from a jar.
121 .replaceFirst("^jar:file:", "jar:file://"));
119 } catch (URISyntaxException e) { 122 } catch (URISyntaxException e) {
120 throw new IllegalStateException("Jar has invalid base resource URI", e); 123 throw new IllegalStateException("Jar has invalid base resource URI", e);
121 } 124 }
@@ -152,17 +155,17 @@ public class ServerLauncher {
152 } 155 }
153 156
154 private static String getListenAddress() { 157 private static String getListenAddress() {
155 var listenAddress = System.getenv("LISTEN_ADDRESS"); 158 var listenAddress = System.getenv("REFINERY_LISTEN_HOST");
156 if (listenAddress == null) { 159 if (listenAddress == null) {
157 return DEFAULT_LISTEN_ADDRESS; 160 return DEFAULT_LISTEN_HOST;
158 } 161 }
159 return listenAddress; 162 return listenAddress;
160 } 163 }
161 164
162 private static int getListenPort() { 165 private static int getListenPort() {
163 var portStr = System.getenv("LISTEN_PORT"); 166 var portStr = System.getenv("REFINERY_LISTEN_PORT");
164 if (portStr != null) { 167 if (portStr != null) {
165 return Integer.parseInt(portStr); 168 return Integer.parseUnsignedInt(portStr);
166 } 169 }
167 return DEFAULT_LISTEN_PORT; 170 return DEFAULT_LISTEN_PORT;
168 } 171 }
@@ -174,7 +177,7 @@ public class ServerLauncher {
174 } 177 }
175 178
176 private static String getPublicHost() { 179 private static String getPublicHost() {
177 var publicHost = System.getenv("PUBLIC_HOST"); 180 var publicHost = System.getenv("REFINERY_PUBLIC_HOST");
178 if (publicHost != null) { 181 if (publicHost != null) {
179 return publicHost.toLowerCase(); 182 return publicHost.toLowerCase();
180 } 183 }
@@ -182,15 +185,15 @@ public class ServerLauncher {
182 } 185 }
183 186
184 private static int getPublicPort() { 187 private static int getPublicPort() {
185 var portStr = System.getenv("PUBLIC_PORT"); 188 var portStr = System.getenv("REFINERY_PUBLIC_PORT");
186 if (portStr != null) { 189 if (portStr != null) {
187 return Integer.parseInt(portStr); 190 return Integer.parseUnsignedInt(portStr);
188 } 191 }
189 return DEFAULT_PUBLIC_PORT; 192 return DEFAULT_PUBLIC_PORT;
190 } 193 }
191 194
192 private static String[] getAllowedOrigins() { 195 private static String[] getAllowedOrigins() {
193 var allowedOrigins = System.getenv("ALLOWED_ORIGINS"); 196 var allowedOrigins = System.getenv("REFINERY_ALLOWED_ORIGINS");
194 if (allowedOrigins != null) { 197 if (allowedOrigins != null) {
195 return allowedOrigins.split(ALLOWED_ORIGINS_SEPARATOR); 198 return allowedOrigins.split(ALLOWED_ORIGINS_SEPARATOR);
196 } 199 }
@@ -219,12 +222,10 @@ public class ServerLauncher {
219 int port; 222 int port;
220 var publicHost = getPublicHost(); 223 var publicHost = getPublicHost();
221 if (publicHost == null) { 224 if (publicHost == null) {
222 host = getListenAddress(); 225 return null;
223 port = getListenPort();
224 } else {
225 host = publicHost;
226 port = getPublicPort();
227 } 226 }
227 host = publicHost;
228 port = getPublicPort();
228 var scheme = port == HTTPS_DEFAULT_PORT ? "wss" : "ws"; 229 var scheme = port == HTTPS_DEFAULT_PORT ? "wss" : "ws";
229 return String.format("%s://%s:%d/xtext-service", scheme, host, port); 230 return String.format("%s://%s:%d/xtext-service", scheme, host, port);
230 } 231 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java
index a2f04e34..7d0a5122 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/config/BackendConfigServlet.java
@@ -25,9 +25,6 @@ public class BackendConfigServlet extends HttpServlet {
25 public void init(ServletConfig config) throws ServletException { 25 public void init(ServletConfig config) throws ServletException {
26 super.init(config); 26 super.init(config);
27 var webSocketUrl = config.getInitParameter(WEBSOCKET_URL_INIT_PARAM); 27 var webSocketUrl = config.getInitParameter(WEBSOCKET_URL_INIT_PARAM);
28 if (webSocketUrl == null) {
29 throw new IllegalArgumentException("Init parameter " + WEBSOCKET_URL_INIT_PARAM + " is mandatory");
30 }
31 var backendConfig = new BackendConfig(webSocketUrl); 28 var backendConfig = new BackendConfig(webSocketUrl);
32 var gson = new Gson(); 29 var gson = new Gson();
33 serializedConfig = gson.toJson(backendConfig); 30 serializedConfig = gson.toJson(backendConfig);
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
new file mode 100644
index 00000000..aa14f39d
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/CancellableSeed.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import tools.refinery.store.map.AnyVersionedMap;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.reasoning.seed.Seed;
13import tools.refinery.store.tuple.Tuple;
14import tools.refinery.viatra.runtime.CancellationToken;
15
16import java.util.Set;
17
18class CancellableSeed<T> implements Seed<T> {
19 private final CancellationToken cancellationToken;
20 private final Seed<T> seed;
21
22 private CancellableSeed(CancellationToken cancellationToken, Seed<T> seed) {
23 this.cancellationToken = cancellationToken;
24 this.seed = seed;
25 }
26
27 @Override
28 public int arity() {
29 return seed.arity();
30 }
31
32 @Override
33 public Class<T> valueType() {
34 return seed.valueType();
35 }
36
37 @Override
38 public T reducedValue() {
39 return seed.reducedValue();
40 }
41
42 @Override
43 public T get(Tuple key) {
44 return seed.get(key);
45 }
46
47 @Override
48 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
49 return new CancellableCursor<>(cancellationToken, seed.getCursor(defaultValue, nodeCount));
50 }
51
52 public static ModelSeed wrap(CancellationToken cancellationToken, ModelSeed modelSeed) {
53 var builder = ModelSeed.builder(modelSeed.getNodeCount());
54 for (var partialSymbol : modelSeed.getSeededSymbols()) {
55 wrap(cancellationToken, (PartialSymbol<?, ?>) partialSymbol, modelSeed, builder);
56 }
57 return builder.build();
58 }
59
60 private static <A, C> void wrap(CancellationToken cancellationToken, PartialSymbol<A, C> partialSymbol,
61 ModelSeed originalModelSeed, ModelSeed.Builder builder) {
62 var originalSeed = originalModelSeed.getSeed(partialSymbol);
63 builder.seed(partialSymbol, new CancellableSeed<>(cancellationToken, originalSeed));
64 }
65
66 private record CancellableCursor<T>(CancellationToken cancellationToken, Cursor<Tuple, T> cursor)
67 implements Cursor<Tuple, T> {
68 @Override
69 public Tuple getKey() {
70 return cursor.getKey();
71 }
72
73 @Override
74 public T getValue() {
75 return cursor.getValue();
76 }
77
78 @Override
79 public boolean isTerminated() {
80 return cursor.isTerminated();
81 }
82
83 @Override
84 public boolean move() {
85 cancellationToken.checkCancelled();
86 return cursor.move();
87 }
88
89 @Override
90 public boolean isDirty() {
91 return cursor.isDirty();
92 }
93
94 @Override
95 public Set<AnyVersionedMap> getDependingMaps() {
96 return cursor.getDependingMaps();
97 }
98 }
99}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
new file mode 100644
index 00000000..ff592e93
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsInternalErrorResult.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8public record SemanticsInternalErrorResult(String error) implements SemanticsResult {
9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
new file mode 100644
index 00000000..644bd179
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsIssuesResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.validation.ValidationResult;
9
10import java.util.List;
11
12public record SemanticsIssuesResult(List<ValidationResult.Issue> issues) implements SemanticsResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
new file mode 100644
index 00000000..a2e19a2f
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.IServiceResult;
9
10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult,
11 SemanticsInternalErrorResult, SemanticsIssuesResult {
12}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
new file mode 100644
index 00000000..26924f0a
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -0,0 +1,142 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonObject;
9import com.google.inject.Inject;
10import com.google.inject.Provider;
11import com.google.inject.Singleton;
12import org.eclipse.xtext.ide.ExecutorServiceProvider;
13import org.eclipse.xtext.service.OperationCanceledManager;
14import org.eclipse.xtext.util.CancelIndicator;
15import org.eclipse.xtext.web.server.model.AbstractCachedService;
16import org.eclipse.xtext.web.server.model.IXtextWebDocument;
17import org.eclipse.xtext.web.server.validation.ValidationService;
18import org.jetbrains.annotations.Nullable;
19import org.slf4j.Logger;
20import org.slf4j.LoggerFactory;
21import tools.refinery.language.model.problem.Problem;
22import tools.refinery.language.web.xtext.server.push.PushWebDocument;
23
24import java.util.List;
25import java.util.Optional;
26import java.util.concurrent.*;
27import java.util.concurrent.atomic.AtomicBoolean;
28
29@Singleton
30public class SemanticsService extends AbstractCachedService<SemanticsResult> {
31 public static final String SEMANTICS_EXECUTOR = "semantics";
32
33 private static final Logger LOG = LoggerFactory.getLogger(SemanticsService.class);
34
35 @Inject
36 private Provider<SemanticsWorker> workerProvider;
37
38 @Inject
39 private OperationCanceledManager operationCanceledManager;
40
41 @Inject
42 private ValidationService validationService;
43
44 private ExecutorService executorService;
45
46 private final long timeoutMs;
47
48 private final long warmupTimeoutMs;
49
50 private final AtomicBoolean warmedUp = new AtomicBoolean(false);
51
52 public SemanticsService() {
53 timeoutMs = getTimeout("REFINERY_SEMANTICS_TIMEOUT_MS").orElse(1000L);
54 warmupTimeoutMs = getTimeout("REFINERY_SEMANTICS_WARMUP_TIMEOUT_MS").orElse(timeoutMs * 2);
55 }
56
57 private static Optional<Long> getTimeout(String name) {
58 return Optional.ofNullable(System.getenv(name)).map(Long::parseUnsignedLong);
59 }
60
61 @Inject
62 public void setExecutorServiceProvider(ExecutorServiceProvider provider) {
63 executorService = provider.get(SEMANTICS_EXECUTOR);
64 }
65
66 @Override
67 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
68 long start = 0;
69 if (LOG.isTraceEnabled()) {
70 start = System.currentTimeMillis();
71 }
72 if (hasError(doc, cancelIndicator)) {
73 return null;
74 }
75 var problem = getProblem(doc);
76 if (problem == null) {
77 return new SemanticsSuccessResult(List.of(), List.of(), new JsonObject());
78 }
79 var worker = workerProvider.get();
80 worker.setProblem(problem, cancelIndicator);
81 var future = executorService.submit(worker);
82 boolean warmedUpCurrently = warmedUp.get();
83 long timeout = warmedUpCurrently ? timeoutMs : warmupTimeoutMs;
84 SemanticsResult result = null;
85 try {
86 result = future.get(timeout, TimeUnit.MILLISECONDS);
87 if (!warmedUpCurrently) {
88 warmedUp.set(true);
89 }
90 } catch (InterruptedException e) {
91 future.cancel(true);
92 LOG.error("Semantics service interrupted", e);
93 Thread.currentThread().interrupt();
94 } catch (ExecutionException e) {
95 operationCanceledManager.propagateAsErrorIfCancelException(e.getCause());
96 LOG.debug("Error while computing semantics", e);
97 if (e.getCause() instanceof Error error) {
98 throw error;
99 }
100 String message = e.getMessage();
101 if (message == null) {
102 message = "Partial interpretation error";
103 }
104 return new SemanticsInternalErrorResult(message);
105 } catch (TimeoutException e) {
106 future.cancel(true);
107 if (!warmedUpCurrently) {
108 warmedUp.set(true);
109 }
110 LOG.trace("Semantics service timeout", e);
111 return new SemanticsInternalErrorResult("Partial interpretation timed out");
112 }
113 if (LOG.isTraceEnabled()) {
114 long end = System.currentTimeMillis();
115 LOG.trace("Computed semantics for {} ({}) in {}ms", doc.getResourceId(), doc.getStateId(),
116 end - start);
117 }
118 return result;
119 }
120
121 private boolean hasError(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
122 if (!(doc instanceof PushWebDocument pushDoc)) {
123 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc);
124 }
125 var validationResult = pushDoc.getCachedServiceResult(validationService, cancelIndicator, true);
126 return validationResult.getIssues().stream()
127 .anyMatch(issue -> "error".equals(issue.getSeverity()));
128 }
129
130 @Nullable
131 private Problem getProblem(IXtextWebDocument doc) {
132 var contents = doc.getResource().getContents();
133 if (contents.isEmpty()) {
134 return null;
135 }
136 var model = contents.get(0);
137 if (!(model instanceof Problem problem)) {
138 return null;
139 }
140 return problem;
141 }
142}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java
new file mode 100644
index 00000000..350b0b2b
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonObject;
9import tools.refinery.language.semantics.metadata.NodeMetadata;
10import tools.refinery.language.semantics.metadata.RelationMetadata;
11
12import java.util.List;
13
14public record SemanticsSuccessResult(List<NodeMetadata> nodes, List<RelationMetadata> relations,
15 JsonObject partialInterpretation) implements SemanticsResult {
16}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
new file mode 100644
index 00000000..8470bb99
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -0,0 +1,175 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject;
10import com.google.inject.Inject;
11import org.eclipse.emf.common.util.Diagnostic;
12import org.eclipse.emf.ecore.EObject;
13import org.eclipse.xtext.service.OperationCanceledManager;
14import org.eclipse.xtext.util.CancelIndicator;
15import org.eclipse.xtext.validation.CheckType;
16import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
17import org.eclipse.xtext.validation.IDiagnosticConverter;
18import org.eclipse.xtext.validation.Issue;
19import org.eclipse.xtext.web.server.validation.ValidationResult;
20import tools.refinery.language.model.problem.Problem;
21import tools.refinery.language.semantics.metadata.MetadataCreator;
22import tools.refinery.language.semantics.model.ModelInitializer;
23import tools.refinery.language.semantics.model.SemanticsUtils;
24import tools.refinery.language.semantics.model.TracedException;
25import tools.refinery.store.map.Cursor;
26import tools.refinery.store.model.Model;
27import tools.refinery.store.model.ModelStore;
28import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
29import tools.refinery.store.reasoning.ReasoningAdapter;
30import tools.refinery.store.reasoning.ReasoningStoreAdapter;
31import tools.refinery.store.reasoning.literal.Concreteness;
32import tools.refinery.store.reasoning.refinement.RefinementResult;
33import tools.refinery.store.reasoning.representation.PartialRelation;
34import tools.refinery.store.reasoning.scope.ScopePropagatorAdapter;
35import tools.refinery.store.reasoning.translator.TranslationException;
36import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
37import tools.refinery.store.tuple.Tuple;
38import tools.refinery.viatra.runtime.CancellationToken;
39
40import java.util.ArrayList;
41import java.util.TreeMap;
42import java.util.concurrent.Callable;
43
44class SemanticsWorker implements Callable<SemanticsResult> {
45 private static final String DIAGNOSTIC_ID = "tools.refinery.language.semantics.SemanticError";
46
47 @Inject
48 private SemanticsUtils semanticsUtils;
49
50 @Inject
51 private OperationCanceledManager operationCanceledManager;
52
53 @Inject
54 private IDiagnosticConverter diagnosticConverter;
55
56 @Inject
57 private ModelInitializer initializer;
58
59 @Inject
60 private MetadataCreator metadataCreator;
61
62 private Problem problem;
63
64 private CancellationToken cancellationToken;
65
66 public void setProblem(Problem problem, CancelIndicator parentIndicator) {
67 this.problem = problem;
68 cancellationToken = () -> {
69 if (Thread.interrupted() || parentIndicator.isCanceled()) {
70 operationCanceledManager.throwOperationCanceledException();
71 }
72 };
73 }
74
75 @Override
76 public SemanticsResult call() {
77 var builder = ModelStore.builder()
78 .with(ViatraModelQueryAdapter.builder()
79 .cancellationToken(cancellationToken))
80 .with(ReasoningAdapter.builder()
81 .requiredInterpretations(Concreteness.PARTIAL))
82 .with(ScopePropagatorAdapter.builder());
83 cancellationToken.checkCancelled();
84 try {
85 var modelSeed = initializer.createModel(problem, builder);
86 cancellationToken.checkCancelled();
87 metadataCreator.setInitializer(initializer);
88 cancellationToken.checkCancelled();
89 var nodesMetadata = metadataCreator.getNodesMetadata();
90 cancellationToken.checkCancelled();
91 var relationsMetadata = metadataCreator.getRelationsMetadata();
92 cancellationToken.checkCancelled();
93 var store = builder.build();
94 cancellationToken.checkCancelled();
95 var cancellableModelSeed = CancellableSeed.wrap(cancellationToken, modelSeed);
96 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(cancellableModelSeed);
97 if (model.getAdapter(ScopePropagatorAdapter.class).propagate() == RefinementResult.REJECTED) {
98 return new SemanticsInternalErrorResult("Scopes are unsatisfiable");
99 }
100 cancellationToken.checkCancelled();
101 var partialInterpretation = getPartialInterpretation(initializer, model);
102
103 return new SemanticsSuccessResult(nodesMetadata, relationsMetadata, partialInterpretation);
104 } catch (TracedException e) {
105 return getTracedErrorResult(e.getSourceElement(), e.getMessage());
106 } catch (TranslationException e) {
107 var sourceElement = initializer.getInverseTrace(e.getPartialSymbol());
108 return getTracedErrorResult(sourceElement, e.getMessage());
109 }
110 }
111
112 private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model) {
113 var adapter = model.getAdapter(ReasoningAdapter.class);
114 var json = new JsonObject();
115 for (var entry : initializer.getRelationTrace().entrySet()) {
116 var relation = entry.getKey();
117 var partialSymbol = entry.getValue();
118 var tuples = getTuplesJson(adapter, partialSymbol);
119 var name = semanticsUtils.getName(relation).orElse(partialSymbol.name());
120 json.add(name, tuples);
121 cancellationToken.checkCancelled();
122 }
123 json.add("builtin::count", getCountJson(model));
124 return json;
125 }
126
127 private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) {
128 var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol);
129 var cursor = interpretation.getAll();
130 return getTuplesJson(cursor);
131 }
132
133 private static JsonArray getTuplesJson(Cursor<Tuple, ?> cursor) {
134 var map = new TreeMap<Tuple, Object>();
135 while (cursor.move()) {
136 map.put(cursor.getKey(), cursor.getValue());
137 }
138 var tuples = new JsonArray();
139 for (var entry : map.entrySet()) {
140 tuples.add(toArray(entry.getKey(), entry.getValue()));
141 }
142 return tuples;
143 }
144
145 private static JsonArray toArray(Tuple tuple, Object value) {
146 int arity = tuple.getSize();
147 var json = new JsonArray(arity + 1);
148 for (int i = 0; i < arity; i++) {
149 json.add(tuple.get(i));
150 }
151 json.add(value.toString());
152 return json;
153 }
154
155 private static JsonArray getCountJson(Model model) {
156 var interpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
157 var cursor = interpretation.getAll();
158 return getTuplesJson(cursor);
159 }
160
161 private SemanticsResult getTracedErrorResult(EObject sourceElement, String message) {
162 if (sourceElement == null || !problem.eResource().equals(sourceElement.eResource())) {
163 return new SemanticsInternalErrorResult(message);
164 }
165 var diagnostic = new FeatureBasedDiagnostic(Diagnostic.ERROR, message, sourceElement, null, 0,
166 CheckType.EXPENSIVE, DIAGNOSTIC_ID);
167 var xtextIssues = new ArrayList<Issue>();
168 diagnosticConverter.convertValidatorDiagnostic(diagnostic, xtextIssues::add);
169 var issues = xtextIssues.stream()
170 .map(issue -> new ValidationResult.Issue(issue.getMessage(), "error", issue.getLineNumber(),
171 issue.getColumn(), issue.getOffset(), issue.getLength()))
172 .toList();
173 return new SemanticsIssuesResult(issues);
174 }
175}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
new file mode 100644
index 00000000..ba26ff58
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/ThreadPoolExecutorServiceProvider.java
@@ -0,0 +1,110 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.xtext.server;
7
8import com.google.inject.Singleton;
9import org.eclipse.xtext.ide.ExecutorServiceProvider;
10import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
11import org.jetbrains.annotations.NotNull;
12import tools.refinery.language.web.semantics.SemanticsService;
13
14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles;
16import java.util.Optional;
17import java.util.concurrent.ExecutorService;
18import java.util.concurrent.Executors;
19import java.util.concurrent.ThreadFactory;
20import java.util.concurrent.atomic.AtomicInteger;
21
22@Singleton
23public class ThreadPoolExecutorServiceProvider extends ExecutorServiceProvider {
24 private static final String DOCUMENT_LOCK_EXECUTOR;
25 private static final AtomicInteger POOL_ID = new AtomicInteger(1);
26
27 private final int executorThreadCount;
28 private final int lockExecutorThreadCount;
29 private final int semanticsExecutorThreadCount;
30
31 static {
32 var lookup = MethodHandles.lookup();
33 MethodHandle getter;
34 try {
35 var privateLookup = MethodHandles.privateLookupIn(XtextWebDocumentAccess.class, lookup);
36 getter = privateLookup.findStaticGetter(XtextWebDocumentAccess.class, "DOCUMENT_LOCK_EXECUTOR",
37 String.class);
38 } catch (IllegalAccessException | NoSuchFieldException e) {
39 throw new IllegalStateException("Failed to find getter", e);
40 }
41 try {
42 DOCUMENT_LOCK_EXECUTOR = (String) getter.invokeExact();
43 } catch (Error e) {
44 // Rethrow JVM errors.
45 throw e;
46 } catch (Throwable e) {
47 throw new IllegalStateException("Failed to get DOCUMENT_LOCK_EXECUTOR", e);
48 }
49 }
50
51 public ThreadPoolExecutorServiceProvider() {
52 executorThreadCount = getCount("REFINERY_XTEXT_THREAD_COUNT").orElse(0);
53 lockExecutorThreadCount = getCount("REFINERY_XTEXT_LOCKING_THREAD_COUNT").orElse(executorThreadCount);
54 semanticsExecutorThreadCount = getCount("REFINERY_XTEXT_SEMANTICS_THREAD_COUNT").orElse(executorThreadCount);
55 }
56
57 private static Optional<Integer> getCount(String name) {
58 return Optional.ofNullable(System.getenv(name)).map(Integer::parseUnsignedInt);
59 }
60
61 @Override
62 protected ExecutorService createInstance(String key) {
63 String name = "xtext-" + POOL_ID.getAndIncrement();
64 if (key != null) {
65 name = name + key + "-";
66 }
67 var threadFactory = new Factory(name, 5);
68 int size = getSize(key);
69 if (size == 0) {
70 return Executors.newCachedThreadPool(threadFactory);
71 }
72 return Executors.newFixedThreadPool(size, threadFactory);
73 }
74
75 private int getSize(String key) {
76 if (SemanticsService.SEMANTICS_EXECUTOR.equals(key)) {
77 return semanticsExecutorThreadCount;
78 } else if (DOCUMENT_LOCK_EXECUTOR.equals(key)) {
79 return lockExecutorThreadCount;
80 } else {
81 return executorThreadCount;
82 }
83 }
84
85 private static class Factory implements ThreadFactory {
86 // We have to explicitly store the {@link ThreadGroup} to create a {@link ThreadFactory}.
87 @SuppressWarnings("squid:S3014")
88 private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
89 private final AtomicInteger threadId = new AtomicInteger(1);
90 private final String namePrefix;
91 private final int priority;
92
93 public Factory(String name, int priority) {
94 namePrefix = name + "-thread-";
95 this.priority = priority;
96 }
97
98 @Override
99 public Thread newThread(@NotNull Runnable runnable) {
100 var thread = new Thread(threadGroup, runnable, namePrefix + threadId.getAndIncrement());
101 if (thread.isDaemon()) {
102 thread.setDaemon(false);
103 }
104 if (thread.getPriority() != priority) {
105 thread.setPriority(priority);
106 }
107 return thread;
108 }
109 }
110}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
index 0135d8f5..74456604 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
@@ -42,6 +42,8 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
42 42
43 private final List<XtextWebPushMessage> pendingPushMessages = new ArrayList<>(); 43 private final List<XtextWebPushMessage> pendingPushMessages = new ArrayList<>();
44 44
45 private volatile boolean disposed;
46
45 public TransactionExecutor(ISession session, IResourceServiceProvider.Registry resourceServiceProviderRegistry) { 47 public TransactionExecutor(ISession session, IResourceServiceProvider.Registry resourceServiceProviderRegistry) {
46 this.session = session; 48 this.session = session;
47 this.resourceServiceProviderRegistry = resourceServiceProviderRegistry; 49 this.resourceServiceProviderRegistry = resourceServiceProviderRegistry;
@@ -52,10 +54,13 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
52 } 54 }
53 55
54 public void handleRequest(XtextWebRequest request) throws ResponseHandlerException { 56 public void handleRequest(XtextWebRequest request) throws ResponseHandlerException {
57 if (disposed) {
58 return;
59 }
55 var serviceContext = new SimpleServiceContext(session, request.getRequestData()); 60 var serviceContext = new SimpleServiceContext(session, request.getRequestData());
56 var ping = serviceContext.getParameter("ping"); 61 var ping = serviceContext.getParameter("ping");
57 if (ping != null) { 62 if (ping != null) {
58 responseHandler.onResponse(new XtextWebOkResponse(request, new PongResult(ping))); 63 onResponse(new XtextWebOkResponse(request, new PongResult(ping)));
59 return; 64 return;
60 } 65 }
61 synchronized (callPendingLock) { 66 synchronized (callPendingLock) {
@@ -72,23 +77,36 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
72 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class); 77 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class);
73 var service = serviceDispatcher.getService(new SubscribingServiceContext(serviceContext, this)); 78 var service = serviceDispatcher.getService(new SubscribingServiceContext(serviceContext, this));
74 var serviceResult = service.getService().apply(); 79 var serviceResult = service.getService().apply();
75 responseHandler.onResponse(new XtextWebOkResponse(request, serviceResult)); 80 onResponse(new XtextWebOkResponse(request, serviceResult));
76 } catch (InvalidRequestException e) { 81 } catch (InvalidRequestException e) {
77 responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.REQUEST_ERROR, e)); 82 onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.REQUEST_ERROR, e));
78 } catch (RuntimeException e) { 83 } catch (RuntimeException e) {
79 responseHandler.onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.SERVER_ERROR, e)); 84 onResponse(new XtextWebErrorResponse(request, XtextWebErrorKind.SERVER_ERROR, e));
80 } finally { 85 } finally {
81 synchronized (callPendingLock) { 86 flushPendingPushMessages();
82 for (var message : pendingPushMessages) { 87 }
83 try { 88 }
84 responseHandler.onResponse(message); 89
85 } catch (ResponseHandlerException | RuntimeException e) { 90 private void onResponse(XtextWebResponse response) throws ResponseHandlerException {
86 LOG.error("Error while flushing push message", e); 91 if (!disposed) {
87 } 92 responseHandler.onResponse(response);
93 }
94 }
95
96 private void flushPendingPushMessages() {
97 synchronized (callPendingLock) {
98 for (var message : pendingPushMessages) {
99 if (disposed) {
100 return;
101 }
102 try {
103 responseHandler.onResponse(message);
104 } catch (ResponseHandlerException | RuntimeException e) {
105 LOG.error("Error while flushing push message", e);
88 } 106 }
89 pendingPushMessages.clear();
90 callPending = false;
91 } 107 }
108 pendingPushMessages.clear();
109 callPending = false;
92 } 110 }
93 } 111 }
94 112
@@ -134,7 +152,7 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
134 * @throws UnknownLanguageException if the Xtext language cannot be determined 152 * @throws UnknownLanguageException if the Xtext language cannot be determined
135 */ 153 */
136 protected Injector getInjector(IServiceContext context) { 154 protected Injector getInjector(IServiceContext context) {
137 IResourceServiceProvider resourceServiceProvider = null; 155 IResourceServiceProvider resourceServiceProvider;
138 var resourceName = context.getParameter("resource"); 156 var resourceName = context.getParameter("resource");
139 if (resourceName == null) { 157 if (resourceName == null) {
140 resourceName = ""; 158 resourceName = "";
@@ -164,10 +182,12 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
164 182
165 @Override 183 @Override
166 public void dispose() { 184 public void dispose() {
185 disposed = true;
167 for (var subscription : subscriptions.values()) { 186 for (var subscription : subscriptions.values()) {
168 var document = subscription.get(); 187 var document = subscription.get();
169 if (document != null) { 188 if (document != null) {
170 document.removePrecomputationListener(this); 189 document.removePrecomputationListener(this);
190 document.cancelBackgroundWork();
171 } 191 }
172 } 192 }
173 } 193 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
index 73527ee5..c3379329 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
@@ -5,12 +5,11 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
7 7
8import java.util.Objects; 8import com.google.gson.annotations.SerializedName;
9
10import org.eclipse.xtext.web.server.IServiceResult; 9import org.eclipse.xtext.web.server.IServiceResult;
11import org.eclipse.xtext.web.server.IUnwrappableServiceResult; 10import org.eclipse.xtext.web.server.IUnwrappableServiceResult;
12 11
13import com.google.gson.annotations.SerializedName; 12import java.util.Objects;
14 13
15public final class XtextWebOkResponse implements XtextWebResponse { 14public final class XtextWebOkResponse implements XtextWebResponse {
16 private String id; 15 private String id;
@@ -19,7 +18,6 @@ public final class XtextWebOkResponse implements XtextWebResponse {
19 private Object responseData; 18 private Object responseData;
20 19
21 public XtextWebOkResponse(String id, Object responseData) { 20 public XtextWebOkResponse(String id, Object responseData) {
22 super();
23 this.id = id; 21 this.id = id;
24 this.responseData = responseData; 22 this.responseData = responseData;
25 } 23 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java
index ff788e94..7c4562bf 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java
@@ -5,19 +5,22 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
7 7
8import com.google.gson.annotations.SerializedName;
9
8import java.util.Map; 10import java.util.Map;
9import java.util.Objects; 11import java.util.Objects;
10 12
11import com.google.gson.annotations.SerializedName;
12
13public class XtextWebRequest { 13public class XtextWebRequest {
14 private String id; 14 private String id;
15 15
16 @SerializedName("request") 16 @SerializedName("request")
17 private Map<String, String> requestData; 17 private Map<String, String> requestData;
18 18
19 public XtextWebRequest() {
20 this(null, null);
21 }
22
19 public XtextWebRequest(String id, Map<String, String> requestData) { 23 public XtextWebRequest(String id, Map<String, String> requestData) {
20 super();
21 this.id = id; 24 this.id = id;
22 this.requestData = requestData; 25 this.requestData = requestData;
23 } 26 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
index 61444c99..c370fb56 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
@@ -5,5 +5,5 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
7 7
8public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { 8public sealed interface XtextWebResponse permits XtextWebOkResponse, XtextWebErrorResponse, XtextWebPushMessage {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
index 4c9135c8..d4a8c433 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
@@ -5,16 +5,28 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
7 7
8import com.google.inject.Inject;
8import org.eclipse.xtext.web.server.IServiceContext; 9import org.eclipse.xtext.web.server.IServiceContext;
9import org.eclipse.xtext.web.server.XtextServiceDispatcher; 10import org.eclipse.xtext.web.server.XtextServiceDispatcher;
11import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry;
10import org.eclipse.xtext.web.server.model.XtextWebDocument; 12import org.eclipse.xtext.web.server.model.XtextWebDocument;
11 13
12import com.google.inject.Singleton; 14import com.google.inject.Singleton;
13 15
16import tools.refinery.language.web.semantics.SemanticsService;
14import tools.refinery.language.web.xtext.server.SubscribingServiceContext; 17import tools.refinery.language.web.xtext.server.SubscribingServiceContext;
15 18
16@Singleton 19@Singleton
17public class PushServiceDispatcher extends XtextServiceDispatcher { 20public class PushServiceDispatcher extends XtextServiceDispatcher {
21 @Inject
22 private SemanticsService semanticsService;
23
24 @Override
25 @Inject
26 protected void registerPreComputedServices(PrecomputedServiceRegistry registry) {
27 super.registerPreComputedServices(registry);
28 registry.addPrecomputedService(semanticsService);
29 }
18 30
19 @Override 31 @Override
20 protected XtextWebDocument getFullTextDocument(String fullText, String resourceId, IServiceContext context) { 32 protected XtextWebDocument getFullTextDocument(String fullText, String resourceId, IServiceContext context) {
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 56fd12c9..2d43fb26 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -5,11 +5,7 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
7 7
8import java.util.ArrayList; 8import com.google.common.collect.ImmutableList;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12
13import org.eclipse.xtext.util.CancelIndicator; 9import org.eclipse.xtext.util.CancelIndicator;
14import org.eclipse.xtext.web.server.IServiceResult; 10import org.eclipse.xtext.web.server.IServiceResult;
15import org.eclipse.xtext.web.server.model.AbstractCachedService; 11import org.eclipse.xtext.web.server.model.AbstractCachedService;
@@ -17,11 +13,13 @@ import org.eclipse.xtext.web.server.model.DocumentSynchronizer;
17import org.eclipse.xtext.web.server.model.XtextWebDocument; 13import org.eclipse.xtext.web.server.model.XtextWebDocument;
18import org.slf4j.Logger; 14import org.slf4j.Logger;
19import org.slf4j.LoggerFactory; 15import org.slf4j.LoggerFactory;
20
21import com.google.common.collect.ImmutableList;
22
23import tools.refinery.language.web.xtext.server.ResponseHandlerException; 16import tools.refinery.language.web.xtext.server.ResponseHandlerException;
24 17
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22
25public class PushWebDocument extends XtextWebDocument { 23public class PushWebDocument extends XtextWebDocument {
26 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class); 24 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class);
27 25
@@ -29,48 +27,44 @@ public class PushWebDocument extends XtextWebDocument {
29 27
30 private final Map<Class<?>, IServiceResult> precomputedServices = new HashMap<>(); 28 private final Map<Class<?>, IServiceResult> precomputedServices = new HashMap<>();
31 29
30 private final DocumentSynchronizer synchronizer;
31
32 public PushWebDocument(String resourceId, DocumentSynchronizer synchronizer) { 32 public PushWebDocument(String resourceId, DocumentSynchronizer synchronizer) {
33 super(resourceId, synchronizer); 33 super(resourceId, synchronizer);
34 if (resourceId == null) { 34 this.synchronizer = synchronizer;
35 throw new IllegalArgumentException("resourceId must not be null");
36 }
37 } 35 }
38 36
39 public boolean addPrecomputationListener(PrecomputationListener listener) { 37 public void addPrecomputationListener(PrecomputationListener listener) {
40 synchronized (precomputationListeners) { 38 synchronized (precomputationListeners) {
41 if (precomputationListeners.contains(listener)) { 39 if (precomputationListeners.contains(listener)) {
42 return false; 40 return;
43 } 41 }
44 precomputationListeners.add(listener); 42 precomputationListeners.add(listener);
45 listener.onSubscribeToPrecomputationEvents(getResourceId(), this); 43 listener.onSubscribeToPrecomputationEvents(getResourceId(), this);
46 return true;
47 } 44 }
48 } 45 }
49 46
50 public boolean removePrecomputationListener(PrecomputationListener listener) { 47 public void removePrecomputationListener(PrecomputationListener listener) {
51 synchronized (precomputationListeners) { 48 synchronized (precomputationListeners) {
52 return precomputationListeners.remove(listener); 49 precomputationListeners.remove(listener);
53 } 50 }
54 } 51 }
55 52
56 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
57 CancelIndicator cancelIndicator, boolean logCacheMiss) { 54 CancelIndicator cancelIndicator, boolean logCacheMiss) {
58 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
59 if (result == null) {
60 LOG.error("{} service returned null result", serviceName);
61 return;
62 }
63 var serviceClass = service.getClass(); 55 var serviceClass = service.getClass();
64 var previousResult = precomputedServices.get(serviceClass); 56 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
65 if (previousResult != null && previousResult.equals(result)) {
66 return;
67 }
68 precomputedServices.put(serviceClass, result); 57 precomputedServices.put(serviceClass, result);
69 notifyPrecomputationListeners(serviceName, result); 58 if (result != null) {
59 notifyPrecomputationListeners(serviceName, result);
60 }
70 } 61 }
71 62
72 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) { 63 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) {
73 var resourceId = getResourceId(); 64 var resourceId = getResourceId();
65 if (resourceId == null) {
66 return;
67 }
74 var stateId = getStateId(); 68 var stateId = getStateId();
75 List<PrecomputationListener> copyOfListeners; 69 List<PrecomputationListener> copyOfListeners;
76 synchronized (precomputationListeners) { 70 synchronized (precomputationListeners) {
@@ -91,4 +85,8 @@ public class PushWebDocument extends XtextWebDocument {
91 } 85 }
92 } 86 }
93 } 87 }
88
89 public void cancelBackgroundWork() {
90 synchronizer.setCanceled(true);
91 }
94} 92}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
index d9e548cd..c72e8e67 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
@@ -18,6 +18,7 @@ import org.eclipse.xtext.web.server.syntaxcoloring.HighlightingService;
18import org.eclipse.xtext.web.server.validation.ValidationService; 18import org.eclipse.xtext.web.server.validation.ValidationService;
19 19
20import com.google.inject.Inject; 20import com.google.inject.Inject;
21import tools.refinery.language.web.semantics.SemanticsService;
21 22
22public class PushWebDocumentAccess extends XtextWebDocumentAccess { 23public class PushWebDocumentAccess extends XtextWebDocumentAccess {
23 24
@@ -49,7 +50,7 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
49 precomputeServiceResult(service, false); 50 precomputeServiceResult(service, false);
50 } 51 }
51 } 52 }
52 53
53 protected <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, boolean logCacheMiss) { 54 protected <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, boolean logCacheMiss) {
54 var serviceName = getPrecomputedServiceName(service); 55 var serviceName = getPrecomputedServiceName(service);
55 readOnly(new CancelableUnitOfWork<Void, IXtextWebDocument>() { 56 readOnly(new CancelableUnitOfWork<Void, IXtextWebDocument>() {
@@ -60,7 +61,7 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
60 } 61 }
61 }); 62 });
62 } 63 }
63 64
64 protected String getPrecomputedServiceName(AbstractCachedService<? extends IServiceResult> service) { 65 protected String getPrecomputedServiceName(AbstractCachedService<? extends IServiceResult> service) {
65 if (service instanceof ValidationService) { 66 if (service instanceof ValidationService) {
66 return "validate"; 67 return "validate";
@@ -68,6 +69,9 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
68 if (service instanceof HighlightingService) { 69 if (service instanceof HighlightingService) {
69 return "highlight"; 70 return "highlight";
70 } 71 }
72 if (service instanceof SemanticsService) {
73 return "semantics";
74 }
71 throw new IllegalArgumentException("Unknown precomputed service: " + service); 75 throw new IllegalArgumentException("Unknown precomputed service: " + service);
72 } 76 }
73} 77}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java
index b6f4fb43..ec6204ef 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentProvider.java
@@ -27,12 +27,7 @@ public class PushWebDocumentProvider implements IWebDocumentProvider {
27 27
28 @Override 28 @Override
29 public XtextWebDocument get(String resourceId, IServiceContext serviceContext) { 29 public XtextWebDocument get(String resourceId, IServiceContext serviceContext) {
30 if (resourceId == null) { 30 return new PushWebDocument(resourceId,
31 return new XtextWebDocument(null, synchronizerProvider.get()); 31 serviceContext.getSession().get(DocumentSynchronizer.class, () -> this.synchronizerProvider.get()));
32 } else {
33 // We only need to send push messages if a resourceId is specified.
34 return new PushWebDocument(resourceId,
35 serviceContext.getSession().get(DocumentSynchronizer.class, () -> this.synchronizerProvider.get()));
36 }
37 } 32 }
38} 33}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java
new file mode 100644
index 00000000..b16cf7df
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java
@@ -0,0 +1,304 @@
1/*
2 * Copyright (C) 2011 Google Inc.
3 * Copyright (C) 2023 The Refinery Authors <https://refinery.tools/>
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * This file was copied into Refinery according to upstream instructions at
20 * https://github.com/google/gson/issues/1104#issuecomment-309582470.
21 * However, we changed the package name below to avoid potential clashes
22 * with other jars on the classpath.
23 */
24package tools.refinery.language.web.xtext.servlet;
25
26import com.google.errorprone.annotations.CanIgnoreReturnValue;
27import com.google.gson.Gson;
28import com.google.gson.JsonElement;
29import com.google.gson.JsonObject;
30import com.google.gson.JsonParseException;
31import com.google.gson.JsonPrimitive;
32import com.google.gson.TypeAdapter;
33import com.google.gson.TypeAdapterFactory;
34import com.google.gson.reflect.TypeToken;
35import com.google.gson.stream.JsonReader;
36import com.google.gson.stream.JsonWriter;
37import java.io.IOException;
38import java.util.LinkedHashMap;
39import java.util.Map;
40
41/**
42 * Adapts values whose runtime type may differ from their declaration type. This
43 * is necessary when a field's type is not the same type that GSON should create
44 * when deserializing that field. For example, consider these types:
45 * <pre> {@code
46 * abstract class Shape {
47 * int x;
48 * int y;
49 * }
50 * class Circle extends Shape {
51 * int radius;
52 * }
53 * class Rectangle extends Shape {
54 * int width;
55 * int height;
56 * }
57 * class Diamond extends Shape {
58 * int width;
59 * int height;
60 * }
61 * class Drawing {
62 * Shape bottomShape;
63 * Shape topShape;
64 * }
65 * }</pre>
66 * <p>Without additional type information, the serialized JSON is ambiguous. Is
67 * the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
68 * {
69 * "bottomShape": {
70 * "width": 10,
71 * "height": 5,
72 * "x": 0,
73 * "y": 0
74 * },
75 * "topShape": {
76 * "radius": 2,
77 * "x": 4,
78 * "y": 1
79 * }
80 * }}</pre>
81 * This class addresses this problem by adding type information to the
82 * serialized JSON and honoring that type information when the JSON is
83 * deserialized: <pre> {@code
84 * {
85 * "bottomShape": {
86 * "type": "Diamond",
87 * "width": 10,
88 * "height": 5,
89 * "x": 0,
90 * "y": 0
91 * },
92 * "topShape": {
93 * "type": "Circle",
94 * "radius": 2,
95 * "x": 4,
96 * "y": 1
97 * }
98 * }}</pre>
99 * Both the type field name ({@code "type"}) and the type labels ({@code
100 * "Rectangle"}) are configurable.
101 *
102 * <h2>Registering Types</h2>
103 * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field
104 * name to the {@link #of} factory method. If you don't supply an explicit type
105 * field name, {@code "type"} will be used. <pre> {@code
106 * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
107 * = RuntimeTypeAdapterFactory.of(Shape.class, "type");
108 * }</pre>
109 * Next register all of your subtypes. Every subtype must be explicitly
110 * registered. This protects your application from injection attacks. If you
111 * don't supply an explicit type label, the type's simple name will be used.
112 * <pre> {@code
113 * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
114 * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
115 * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
116 * }</pre>
117 * Finally, register the type adapter factory in your application's GSON builder:
118 * <pre> {@code
119 * Gson gson = new GsonBuilder()
120 * .registerTypeAdapterFactory(shapeAdapterFactory)
121 * .create();
122 * }</pre>
123 * Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
124 * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
125 * .registerSubtype(Rectangle.class)
126 * .registerSubtype(Circle.class)
127 * .registerSubtype(Diamond.class);
128 * }</pre>
129 *
130 * <h2>Serialization and deserialization</h2>
131 * In order to serialize and deserialize a polymorphic object,
132 * you must specify the base type explicitly.
133 * <pre> {@code
134 * Diamond diamond = new Diamond();
135 * String json = gson.toJson(diamond, Shape.class);
136 * }</pre>
137 * And then:
138 * <pre> {@code
139 * Shape shape = gson.fromJson(json, Shape.class);
140 * }</pre>
141 */
142public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
143 private final Class<?> baseType;
144 private final String typeFieldName;
145 private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
146 private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
147 private final boolean maintainType;
148 private boolean recognizeSubtypes;
149
150 private RuntimeTypeAdapterFactory(
151 Class<?> baseType, String typeFieldName, boolean maintainType) {
152 if (typeFieldName == null || baseType == null) {
153 throw new NullPointerException();
154 }
155 this.baseType = baseType;
156 this.typeFieldName = typeFieldName;
157 this.maintainType = maintainType;
158 }
159
160 /**
161 * Creates a new runtime type adapter using for {@code baseType} using {@code
162 * typeFieldName} as the type field name. Type field names are case sensitive.
163 *
164 * @param maintainType true if the type field should be included in deserialized objects
165 */
166 public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
167 return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
168 }
169
170 /**
171 * Creates a new runtime type adapter using for {@code baseType} using {@code
172 * typeFieldName} as the type field name. Type field names are case sensitive.
173 */
174 public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
175 return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
176 }
177
178 /**
179 * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
180 * the type field name.
181 */
182 public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
183 return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
184 }
185
186 /**
187 * Ensures that this factory will handle not just the given {@code baseType}, but any subtype
188 * of that type.
189 */
190 @CanIgnoreReturnValue
191 public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
192 this.recognizeSubtypes = true;
193 return this;
194 }
195
196 /**
197 * Registers {@code type} identified by {@code label}. Labels are case
198 * sensitive.
199 *
200 * @throws IllegalArgumentException if either {@code type} or {@code label}
201 * have already been registered on this type adapter.
202 */
203 @CanIgnoreReturnValue
204 public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
205 if (type == null || label == null) {
206 throw new NullPointerException();
207 }
208 if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
209 throw new IllegalArgumentException("types and labels must be unique");
210 }
211 labelToSubtype.put(label, type);
212 subtypeToLabel.put(type, label);
213 return this;
214 }
215
216 /**
217 * Registers {@code type} identified by its {@link Class#getSimpleName simple
218 * name}. Labels are case sensitive.
219 *
220 * @throws IllegalArgumentException if either {@code type} or its simple name
221 * have already been registered on this type adapter.
222 */
223 @CanIgnoreReturnValue
224 public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
225 return registerSubtype(type, type.getSimpleName());
226 }
227
228 @Override
229 public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
230 if (type == null) {
231 return null;
232 }
233 Class<?> rawType = type.getRawType();
234 boolean handle =
235 recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
236 if (!handle) {
237 return null;
238 }
239
240 final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
241 final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
242 final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
243 for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
244 TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
245 labelToDelegate.put(entry.getKey(), delegate);
246 subtypeToDelegate.put(entry.getValue(), delegate);
247 }
248
249 return new TypeAdapter<R>() {
250 @Override public R read(JsonReader in) throws IOException {
251 JsonElement jsonElement = jsonElementAdapter.read(in);
252 JsonElement labelJsonElement;
253 if (maintainType) {
254 labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
255 } else {
256 labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
257 }
258
259 if (labelJsonElement == null) {
260 throw new JsonParseException("cannot deserialize " + baseType
261 + " because it does not define a field named " + typeFieldName);
262 }
263 String label = labelJsonElement.getAsString();
264 @SuppressWarnings("unchecked") // registration requires that subtype extends T
265 TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
266 if (delegate == null) {
267 throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
268 + label + "; did you forget to register a subtype?");
269 }
270 return delegate.fromJsonTree(jsonElement);
271 }
272
273 @Override public void write(JsonWriter out, R value) throws IOException {
274 Class<?> srcType = value.getClass();
275 String label = subtypeToLabel.get(srcType);
276 @SuppressWarnings("unchecked") // registration requires that subtype extends T
277 TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
278 if (delegate == null) {
279 throw new JsonParseException("cannot serialize " + srcType.getName()
280 + "; did you forget to register a subtype?");
281 }
282 JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
283
284 if (maintainType) {
285 jsonElementAdapter.write(out, jsonObject);
286 return;
287 }
288
289 JsonObject clone = new JsonObject();
290
291 if (jsonObject.has(typeFieldName)) {
292 throw new JsonParseException("cannot serialize " + srcType.getName()
293 + " because it already defines a field named " + typeFieldName);
294 }
295 clone.add(typeFieldName, new JsonPrimitive(label));
296
297 for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
298 clone.add(e.getKey(), e.getValue());
299 }
300 jsonElementAdapter.write(out, clone);
301 }
302 }.nullSafe();
303 }
304}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java
index 043d318c..1fde1be5 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java
@@ -6,6 +6,7 @@
6package tools.refinery.language.web.xtext.servlet; 6package tools.refinery.language.web.xtext.servlet;
7 7
8import com.google.gson.Gson; 8import com.google.gson.Gson;
9import com.google.gson.GsonBuilder;
9import com.google.gson.JsonIOException; 10import com.google.gson.JsonIOException;
10import com.google.gson.JsonParseException; 11import com.google.gson.JsonParseException;
11import org.eclipse.jetty.websocket.api.Callback; 12import org.eclipse.jetty.websocket.api.Callback;
@@ -16,6 +17,7 @@ import org.eclipse.xtext.resource.IResourceServiceProvider;
16import org.eclipse.xtext.web.server.ISession; 17import org.eclipse.xtext.web.server.ISession;
17import org.slf4j.Logger; 18import org.slf4j.Logger;
18import org.slf4j.LoggerFactory; 19import org.slf4j.LoggerFactory;
20import tools.refinery.language.semantics.metadata.*;
19import tools.refinery.language.web.xtext.server.ResponseHandler; 21import tools.refinery.language.web.xtext.server.ResponseHandler;
20import tools.refinery.language.web.xtext.server.ResponseHandlerException; 22import tools.refinery.language.web.xtext.server.ResponseHandlerException;
21import tools.refinery.language.web.xtext.server.TransactionExecutor; 23import tools.refinery.language.web.xtext.server.TransactionExecutor;
@@ -28,7 +30,15 @@ import java.io.Reader;
28public class XtextWebSocket implements ResponseHandler { 30public class XtextWebSocket implements ResponseHandler {
29 private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); 31 private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class);
30 32
31 private final Gson gson = new Gson(); 33 private final Gson gson = new GsonBuilder()
34 .disableJdkUnsafe()
35 .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(RelationDetail.class, "type")
36 .registerSubtype(ClassDetail.class, "class")
37 .registerSubtype(ReferenceDetail.class, "reference")
38 .registerSubtype(OppositeReferenceDetail.class, "opposite")
39 .registerSubtype(PredicateDetail.class, "predicate")
40 .registerSubtype(BuiltInDetail.class, "builtin"))
41 .create();
32 42
33 private final TransactionExecutor executor; 43 private final TransactionExecutor executor;
34 44
@@ -70,10 +80,11 @@ public class XtextWebSocket implements ResponseHandler {
70 80
71 @OnWebSocketError 81 @OnWebSocketError
72 public void onError(Throwable error) { 82 public void onError(Throwable error) {
83 executor.dispose();
73 if (webSocketSession == null) { 84 if (webSocketSession == null) {
74 return; 85 return;
75 } 86 }
76 LOG.error("Internal websocket error in connection from" + webSocketSession.getRemoteSocketAddress(), error); 87 LOG.error("Internal websocket error in connection from " + webSocketSession.getRemoteSocketAddress(), error);
77 } 88 }
78 89
79 @OnWebSocketMessage 90 @OnWebSocketMessage
@@ -86,14 +97,18 @@ public class XtextWebSocket implements ResponseHandler {
86 try { 97 try {
87 request = gson.fromJson(reader, XtextWebRequest.class); 98 request = gson.fromJson(reader, XtextWebRequest.class);
88 } catch (JsonIOException e) { 99 } catch (JsonIOException e) {
89 LOG.error("Cannot read from websocket from" + webSocketSession.getRemoteSocketAddress(), e); 100 LOG.error("Cannot read from websocket from " + webSocketSession.getRemoteSocketAddress(), e);
90 if (webSocketSession.isOpen()) { 101 if (webSocketSession.isOpen()) {
102 executor.dispose();
91 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload", Callback.NOOP); 103 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload", Callback.NOOP);
92 } 104 }
93 return; 105 return;
94 } catch (JsonParseException e) { 106 } catch (JsonParseException e) {
95 LOG.warn("Malformed websocket request from" + webSocketSession.getRemoteSocketAddress(), e); 107 LOG.warn("Malformed websocket request from " + webSocketSession.getRemoteSocketAddress(), e);
96 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload", Callback.NOOP); 108 if (webSocketSession.isOpen()) {
109 executor.dispose();
110 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload", Callback.NOOP);
111 }
97 return; 112 return;
98 } 113 }
99 try { 114 try {
@@ -101,6 +116,7 @@ public class XtextWebSocket implements ResponseHandler {
101 } catch (ResponseHandlerException e) { 116 } catch (ResponseHandlerException e) {
102 LOG.warn("Cannot write websocket response", e); 117 LOG.warn("Cannot write websocket response", e);
103 if (webSocketSession.isOpen()) { 118 if (webSocketSession.isOpen()) {
119 executor.dispose();
104 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write response", Callback.NOOP); 120 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write response", Callback.NOOP);
105 } 121 }
106 } 122 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 927eeab1..889a55cb 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -93,7 +93,7 @@ class ProblemWebSocketServletIntegrationTest {
93 clientSocket.waitForTestResult(); 93 clientSocket.waitForTestResult();
94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); 94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
95 var responses = clientSocket.getResponses(); 95 var responses = clientSocket.getResponses();
96 assertThat(responses, hasSize(5)); 96 assertThat(responses, hasSize(8));
97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); 97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
98 assertThat(responses.get(1), startsWith( 98 assertThat(responses.get(1), startsWith(
99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," + 99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," +
@@ -101,10 +101,19 @@ class ProblemWebSocketServletIntegrationTest {
101 assertThat(responses.get(2), equalTo( 101 assertThat(responses.get(2), equalTo(
102 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\"," + 102 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\"," +
103 "\"push\":{\"issues\":[]}}")); 103 "\"push\":{\"issues\":[]}}"));
104 assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); 104 assertThat(responses.get(3), startsWith(
105 assertThat(responses.get(4), startsWith( 105 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"semantics\"," +
106 "\"push\":{"));
107 assertThat(responses.get(4), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}"));
108 assertThat(responses.get(5), startsWith(
106 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," + 109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," +
107 "\"push\":{\"regions\":[")); 110 "\"push\":{\"regions\":["));
111 assertThat(responses.get(6), equalTo(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"validate\"," +
113 "\"push\":{\"issues\":[]}}"));
114 assertThat(responses.get(7), startsWith(
115 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," +
116 "\"push\":{"));
108 } 117 }
109 118
110 @WebSocket 119 @WebSocket
@@ -117,14 +126,14 @@ class ProblemWebSocketServletIntegrationTest {
117 "\"fullText\":\"class Person.\n\"}}", 126 "\"fullText\":\"class Person.\n\"}}",
118 Callback.NOOP 127 Callback.NOOP
119 ); 128 );
120 case 3 -> //noinspection TextBlockMigration 129 case 4 -> //noinspection TextBlockMigration
121 session.sendText( 130 session.sendText(
122 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," + 131 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," +
123 "\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\"," + 132 "\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\"," +
124 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}", 133 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}",
125 Callback.NOOP 134 Callback.NOOP
126 ); 135 );
127 case 5 -> session.close(); 136 case 8 -> session.close();
128 } 137 }
129 } 138 }
130 } 139 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
index 4a5eed95..e9d889c4 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/ProblemWebInjectorProvider.java
@@ -34,6 +34,7 @@ public class ProblemWebInjectorProvider extends ProblemInjectorProvider {
34 // the tasks in the service and the {@link 34 // the tasks in the service and the {@link
35 // org.eclipse.xtext.testing.extensions.InjectionExtension}. 35 // org.eclipse.xtext.testing.extensions.InjectionExtension}.
36 return new ProblemWebModule() { 36 return new ProblemWebModule() {
37 @Override
37 @SuppressWarnings("unused") 38 @SuppressWarnings("unused")
38 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() { 39 public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() {
39 return AwaitTerminationExecutorServiceProvider.class; 40 return AwaitTerminationExecutorServiceProvider.class;
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
index 09079aa8..991ff114 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
@@ -35,7 +35,7 @@ public class RestartableCachedThreadPool implements ExecutorService {
35 public void waitForTermination() { 35 public void waitForTermination() {
36 boolean result = false; 36 boolean result = false;
37 try { 37 try {
38 result = delegate.awaitTermination(1, TimeUnit.SECONDS); 38 result = delegate.awaitTermination(10, TimeUnit.SECONDS);
39 } catch (InterruptedException e) { 39 } catch (InterruptedException e) {
40 LOG.warn("Interrupted while waiting for delegate executor to stop", e); 40 LOG.warn("Interrupted while waiting for delegate executor to stop", e);
41 } 41 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index 841bacd3..22ce1b47 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test;
18import org.junit.jupiter.api.extension.ExtendWith; 18import org.junit.jupiter.api.extension.ExtendWith;
19import org.mockito.ArgumentCaptor; 19import org.mockito.ArgumentCaptor;
20import org.mockito.junit.jupiter.MockitoExtension; 20import org.mockito.junit.jupiter.MockitoExtension;
21import tools.refinery.language.web.semantics.SemanticsService;
21import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider; 22import tools.refinery.language.web.tests.AwaitTerminationExecutorServiceProvider;
22import tools.refinery.language.web.tests.ProblemWebInjectorProvider; 23import tools.refinery.language.web.tests.ProblemWebInjectorProvider;
23import tools.refinery.language.web.xtext.server.ResponseHandler; 24import tools.refinery.language.web.xtext.server.ResponseHandler;
@@ -59,11 +60,16 @@ class TransactionExecutorTest {
59 @Inject 60 @Inject
60 private AwaitTerminationExecutorServiceProvider executorServices; 61 private AwaitTerminationExecutorServiceProvider executorServices;
61 62
63 @Inject
64 private SemanticsService semanticsService;
65
62 private TransactionExecutor transactionExecutor; 66 private TransactionExecutor transactionExecutor;
63 67
64 @BeforeEach 68 @BeforeEach
65 void beforeEach() { 69 void beforeEach() {
66 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry); 70 transactionExecutor = new TransactionExecutor(new SimpleSession(), resourceServiceProviderRegistry);
71 // Manually re-create the semantics analysis thread pool if it was disposed by the previous test.
72 semanticsService.setExecutorServiceProvider(executorServices);
67 } 73 }
68 74
69 @Test 75 @Test
@@ -95,7 +101,7 @@ class TransactionExecutorTest {
95 "0"))); 101 "0")));
96 102
97 var captor = newCaptor(); 103 var captor = newCaptor();
98 verify(responseHandler, times(2)).onResponse(captor.capture()); 104 verify(responseHandler, times(4)).onResponse(captor.capture());
99 var newStateId = getStateId("bar", captor.getAllValues().get(0)); 105 var newStateId = getStateId("bar", captor.getAllValues().get(0));
100 assertHighlightingResponse(newStateId, captor.getAllValues().get(1)); 106 assertHighlightingResponse(newStateId, captor.getAllValues().get(1));
101 } 107 }
@@ -126,7 +132,7 @@ class TransactionExecutorTest {
126 private String updateFullText(ArgumentCaptor<XtextWebResponse> captor) throws ResponseHandlerException { 132 private String updateFullText(ArgumentCaptor<XtextWebResponse> captor) throws ResponseHandlerException {
127 var responseHandler = sendRequestAndWaitForAllResponses(new XtextWebRequest("foo", UPDATE_FULL_TEXT_PARAMS)); 133 var responseHandler = sendRequestAndWaitForAllResponses(new XtextWebRequest("foo", UPDATE_FULL_TEXT_PARAMS));
128 134
129 verify(responseHandler, times(3)).onResponse(captor.capture()); 135 verify(responseHandler, times(4)).onResponse(captor.capture());
130 return getStateId("foo", captor.getAllValues().get(0)); 136 return getStateId("foo", captor.getAllValues().get(0));
131 } 137 }
132 138
diff --git a/subprojects/language/build.gradle.kts b/subprojects/language/build.gradle.kts
index bac1e586..a6324e03 100644
--- a/subprojects/language/build.gradle.kts
+++ b/subprojects/language/build.gradle.kts
@@ -51,8 +51,8 @@ val generateXtextLanguage by tasks.registering(JavaExec::class) {
51 inputs.file("../language-model/src/main/resources/model/problem.genmodel") 51 inputs.file("../language-model/src/main/resources/model/problem.genmodel")
52 outputs.dir("src/main/xtext-gen") 52 outputs.dir("src/main/xtext-gen")
53 outputs.dir("src/testFixtures/xtext-gen") 53 outputs.dir("src/testFixtures/xtext-gen")
54 outputs.dir("$buildDir/generated/sources/xtext/ide") 54 outputs.dir(layout.buildDirectory.dir("generated/sources/xtext/ide"))
55 outputs.dir("$buildDir/generated/sources/xtext/web") 55 outputs.dir(layout.buildDirectory.dir("generated/sources/xtext/web"))
56 args("src/main/java/tools/refinery/language/GenerateProblem.mwe2", "-p", "rootPath=/$projectDir/..") 56 args("src/main/java/tools/refinery/language/GenerateProblem.mwe2", "-p", "rootPath=/$projectDir/..")
57} 57}
58 58
@@ -85,11 +85,11 @@ tasks {
85} 85}
86 86
87artifacts { 87artifacts {
88 add(generatedIdeSources.name, file("$buildDir/generated/sources/xtext/ide")) { 88 add(generatedIdeSources.name, layout.buildDirectory.dir("generated/sources/xtext/ide")) {
89 builtBy(generateXtextLanguage) 89 builtBy(generateXtextLanguage)
90 } 90 }
91 91
92 add(generatedWebSources.name, file("$buildDir/generated/sources/xtext/web")) { 92 add(generatedWebSources.name, layout.buildDirectory.dir("generated/sources/xtext/web")) {
93 builtBy(generateXtextLanguage) 93 builtBy(generateXtextLanguage)
94 } 94 }
95} 95}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
index 9e330347..0a91178b 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
+++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext
@@ -14,7 +14,7 @@ Problem:
14 14
15Statement: 15Statement:
16 Assertion | ClassDeclaration | EnumDeclaration | 16 Assertion | ClassDeclaration | EnumDeclaration |
17 PredicateDefinition | FunctionDefinition | /* RuleDefinition | */ 17 PredicateDefinition | /* FunctionDefinition | RuleDefinition | */
18 ScopeDeclaration | IndividualDeclaration; 18 ScopeDeclaration | IndividualDeclaration;
19 19
20ClassDeclaration: 20ClassDeclaration:
@@ -32,7 +32,7 @@ EnumLiteral returns Node:
32 name=Identifier; 32 name=Identifier;
33 33
34FeatureDeclaration: 34FeatureDeclaration:
35 ReferenceDeclaration | AttributeDeclaration | FlagDeclaration; 35 ReferenceDeclaration /* | AttributeDeclaration | FlagDeclaration */;
36 36
37enum ReferenceKind: 37enum ReferenceKind:
38 REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container"; 38 REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container";
@@ -44,23 +44,17 @@ ReferenceDeclaration:
44 name=Identifier 44 name=Identifier
45 ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?; 45 ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?;
46 46
47enum PrimitiveType: 47//enum PrimitiveType:
48 INT="int" | REAL="real" | STRING="string"; 48// INT="int" | REAL="real" | STRING="string";
49 49//
50AttributeDeclaration: 50//AttributeDeclaration:
51 attributeType=PrimitiveType name=Identifier; 51// attributeType=PrimitiveType name=Identifier;
52 52//
53FlagDeclaration: 53//FlagDeclaration:
54 "bool" name=Identifier; 54// "bool" name=Identifier;
55
56enum ErrorKind returns PredicateKind:
57 ERROR="error";
58
59enum PredicateKind:
60 ERROR="error" | CONTAINED="contained" | CONTAINMENT="containment";
61 55
62PredicateDefinition: 56PredicateDefinition:
63 (kind=ErrorKind | kind=PredicateKind? "pred") 57 ("pred" | error?="error" "pred"?)
64 name=Identifier 58 name=Identifier
65 "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" 59 "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")"
66 ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)? 60 ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)?
@@ -69,14 +63,14 @@ PredicateDefinition:
69Conjunction: 63Conjunction:
70 literals+=Expr ("," literals+=Expr)*; 64 literals+=Expr ("," literals+=Expr)*;
71 65
72FunctionDefinition: 66//FunctionDefinition:
73 "fn" functionType=PrimitiveType name=Identifier 67// "fn" functionType=PrimitiveType name=Identifier
74 "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" 68// "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")"
75 ("=" cases+=Case (";" cases+=Case)*)? 69// ("=" cases+=Case (";" cases+=Case)*)?
76 "."; 70// ".";
77 71//
78Case: 72//Case:
79 Conjunction ({Match.condition=current} "->" value=Expr)?; 73// Conjunction ({Match.condition=current} "->" value=Expr)?;
80 74
81//RuleDefinition: 75//RuleDefinition:
82// "rule" 76// "rule"
@@ -87,7 +81,7 @@ Case:
87// "."; 81// ".";
88 82
89Parameter: 83Parameter:
90 (modality=Modality? parameterType=[Relation|QualifiedName])? name=Identifier; 84 parameterType=[Relation|QualifiedName]? name=Identifier;
91 85
92//Consequent: 86//Consequent:
93// actions+=Action ("," actions+=Action)*; 87// actions+=Action ("," actions+=Action)*;
@@ -268,7 +262,7 @@ NonContainmentQualifiedName hidden():
268 NonContainmentIdentifier ("::" Identifier)*; 262 NonContainmentIdentifier ("::" Identifier)*;
269 263
270Identifier: 264Identifier:
271 NonContainmentIdentifier | "contains"; 265 NonContainmentIdentifier | "contains" | "container";
272 266
273NonContainmentIdentifier: 267NonContainmentIdentifier:
274 ID | "contained" | "sum" | "prod" | "min" | "max"; 268 ID | "contained" | "sum" | "prod" | "min" | "max";
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java
index b145ef27..31eb55a6 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemDerivedStateComputer.java
@@ -18,6 +18,7 @@ import org.eclipse.xtext.resource.DerivedStateAwareResource;
18import org.eclipse.xtext.resource.IDerivedStateComputer; 18import org.eclipse.xtext.resource.IDerivedStateComputer;
19import org.eclipse.xtext.resource.XtextResource; 19import org.eclipse.xtext.resource.XtextResource;
20import tools.refinery.language.model.problem.*; 20import tools.refinery.language.model.problem.*;
21import tools.refinery.language.utils.ProblemUtil;
21 22
22import java.util.*; 23import java.util.*;
23import java.util.function.Function; 24import java.util.function.Function;
@@ -58,7 +59,7 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer {
58 } 59 }
59 60
60 protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) { 61 protected void installDerivedProblemState(Problem problem, Adapter adapter, boolean preLinkingPhase) {
61 installNewNodes(problem, adapter); 62 installDerivedClassDeclarationState(problem, adapter);
62 if (preLinkingPhase) { 63 if (preLinkingPhase) {
63 return; 64 return;
64 } 65 }
@@ -66,24 +67,67 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer {
66 derivedVariableComputer.installDerivedVariables(problem, nodeNames); 67 derivedVariableComputer.installDerivedVariables(problem, nodeNames);
67 } 68 }
68 69
69 protected void installNewNodes(Problem problem, Adapter adapter) { 70 protected void installDerivedClassDeclarationState(Problem problem, Adapter adapter) {
70 for (Statement statement : problem.getStatements()) { 71 for (var statement : problem.getStatements()) {
71 if (statement instanceof ClassDeclaration declaration && !declaration.isAbstract() 72 if (statement instanceof ClassDeclaration classDeclaration) {
72 && declaration.getNewNode() == null) { 73 installOrRemoveNewNode(adapter, classDeclaration);
74 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
75 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
76 installOrRemoveInvalidMultiplicityPredicate(adapter, classDeclaration, referenceDeclaration);
77 }
78 }
79 }
80 }
81 }
82
83 protected void installOrRemoveNewNode(Adapter adapter, ClassDeclaration declaration) {
84 if (declaration.isAbstract()) {
85 var newNode = declaration.getNewNode();
86 if (newNode != null) {
87 declaration.setNewNode(null);
88 adapter.removeNewNode(declaration);
89 }
90 } else {
91 if (declaration.getNewNode() == null) {
73 var newNode = adapter.createNewNodeIfAbsent(declaration, key -> createNode(NEW_NODE)); 92 var newNode = adapter.createNewNodeIfAbsent(declaration, key -> createNode(NEW_NODE));
74 declaration.setNewNode(newNode); 93 declaration.setNewNode(newNode);
75 } 94 }
76 } 95 }
77 } 96 }
78 97
98 protected void installOrRemoveInvalidMultiplicityPredicate(
99 Adapter adapter, ClassDeclaration containingClassDeclaration, ReferenceDeclaration declaration) {
100 if (ProblemUtil.hasMultiplicityConstraint(declaration)) {
101 if (declaration.getInvalidMultiplicity() == null) {
102 var invalidMultiplicity = adapter.createInvalidMultiplicityPredicateIfAbsent(declaration, key -> {
103 var predicate = ProblemFactory.eINSTANCE.createPredicateDefinition();
104 predicate.setError(true);
105 predicate.setName("invalidMultiplicity");
106 var parameter = ProblemFactory.eINSTANCE.createParameter();
107 parameter.setParameterType(containingClassDeclaration);
108 parameter.setName("node");
109 predicate.getParameters().add(parameter);
110 return predicate;
111 });
112 declaration.setInvalidMultiplicity(invalidMultiplicity);
113 }
114 } else {
115 var invalidMultiplicity = declaration.getInvalidMultiplicity();
116 if (invalidMultiplicity != null) {
117 declaration.setInvalidMultiplicity(null);
118 adapter.removeInvalidMultiplicityPredicate(declaration);
119 }
120 }
121 }
122
79 protected Set<String> installDerivedNodes(Problem problem) { 123 protected Set<String> installDerivedNodes(Problem problem) {
80 var collector = nodeNameCollectorProvider.get(); 124 var collector = nodeNameCollectorProvider.get();
81 collector.collectNodeNames(problem); 125 collector.collectNodeNames(problem);
82 Set<String> nodeNames = collector.getNodeNames(); 126 Set<String> nodeNames = collector.getNodeNames();
83 List<Node> grapNodes = problem.getNodes(); 127 List<Node> graphNodes = problem.getNodes();
84 for (String nodeName : nodeNames) { 128 for (String nodeName : nodeNames) {
85 var graphNode = createNode(nodeName); 129 var graphNode = createNode(nodeName);
86 grapNodes.add(graphNode); 130 graphNodes.add(graphNode);
87 } 131 }
88 return nodeNames; 132 return nodeNames;
89 } 133 }
@@ -104,15 +148,24 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer {
104 } 148 }
105 149
106 protected void discardDerivedProblemState(Problem problem, Adapter adapter) { 150 protected void discardDerivedProblemState(Problem problem, Adapter adapter) {
107 Set<ClassDeclaration> classDeclarations = new HashSet<>(); 151 var abstractClassDeclarations = new HashSet<ClassDeclaration>();
152 var referenceDeclarationsWithMultiplicity = new HashSet<ReferenceDeclaration>();
108 problem.getNodes().clear(); 153 problem.getNodes().clear();
109 for (var statement : problem.getStatements()) { 154 for (var statement : problem.getStatements()) {
110 if (statement instanceof ClassDeclaration classDeclaration) { 155 if (statement instanceof ClassDeclaration classDeclaration) {
111 classDeclaration.setNewNode(null); 156 classDeclaration.setNewNode(null);
112 classDeclarations.add(classDeclaration); 157 if (classDeclaration.isAbstract()) {
158 abstractClassDeclarations.add(classDeclaration);
159 }
160 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
161 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration &&
162 ProblemUtil.hasMultiplicityConstraint(referenceDeclaration)) {
163 referenceDeclarationsWithMultiplicity.add(referenceDeclaration);
164 }
165 }
113 } 166 }
114 } 167 }
115 adapter.retainAll(classDeclarations); 168 adapter.retainAll(abstractClassDeclarations, referenceDeclarationsWithMultiplicity);
116 derivedVariableComputer.discardDerivedVariables(problem); 169 derivedVariableComputer.discardDerivedVariables(problem);
117 } 170 }
118 171
@@ -134,14 +187,31 @@ public class ProblemDerivedStateComputer implements IDerivedStateComputer {
134 187
135 protected static class Adapter extends AdapterImpl { 188 protected static class Adapter extends AdapterImpl {
136 private final Map<ClassDeclaration, Node> newNodes = new HashMap<>(); 189 private final Map<ClassDeclaration, Node> newNodes = new HashMap<>();
190 private final Map<ReferenceDeclaration, PredicateDefinition> invalidMultiplicityPredicates = new HashMap<>();
137 191
138 public Node createNewNodeIfAbsent(ClassDeclaration classDeclaration, 192 public Node createNewNodeIfAbsent(ClassDeclaration classDeclaration,
139 Function<ClassDeclaration, Node> createNode) { 193 Function<ClassDeclaration, Node> createNode) {
140 return newNodes.computeIfAbsent(classDeclaration, createNode); 194 return newNodes.computeIfAbsent(classDeclaration, createNode);
141 } 195 }
142 196
143 public void retainAll(Collection<ClassDeclaration> classDeclarations) { 197 public void removeNewNode(ClassDeclaration classDeclaration) {
144 newNodes.keySet().retainAll(classDeclarations); 198 newNodes.remove(classDeclaration);
199 }
200
201 public PredicateDefinition createInvalidMultiplicityPredicateIfAbsent(
202 ReferenceDeclaration referenceDeclaration,
203 Function<ReferenceDeclaration, PredicateDefinition> createPredicate) {
204 return invalidMultiplicityPredicates.computeIfAbsent(referenceDeclaration, createPredicate);
205 }
206
207 public void removeInvalidMultiplicityPredicate(ReferenceDeclaration referenceDeclaration) {
208 invalidMultiplicityPredicates.remove(referenceDeclaration);
209 }
210
211 public void retainAll(Collection<ClassDeclaration> abstractClassDeclarations,
212 Collection<ReferenceDeclaration> referenceDeclarationsWithMultiplicity) {
213 newNodes.keySet().retainAll(abstractClassDeclarations);
214 invalidMultiplicityPredicates.keySet().retainAll(referenceDeclarationsWithMultiplicity);
145 } 215 }
146 216
147 @Override 217 @Override
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java
index 1fe2df89..29eaad84 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemLocationInFileProvider.java
@@ -8,7 +8,6 @@ package tools.refinery.language.resource;
8import org.eclipse.emf.ecore.EObject; 8import org.eclipse.emf.ecore.EObject;
9import org.eclipse.xtext.resource.DefaultLocationInFileProvider; 9import org.eclipse.xtext.resource.DefaultLocationInFileProvider;
10import org.eclipse.xtext.util.ITextRegion; 10import org.eclipse.xtext.util.ITextRegion;
11
12import tools.refinery.language.model.problem.ImplicitVariable; 11import tools.refinery.language.model.problem.ImplicitVariable;
13import tools.refinery.language.model.problem.Node; 12import tools.refinery.language.model.problem.Node;
14import tools.refinery.language.utils.ProblemUtil; 13import tools.refinery.language.utils.ProblemUtil;
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
index 630be379..a16f77eb 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
@@ -5,6 +5,9 @@
5 */ 5 */
6package tools.refinery.language.resource; 6package tools.refinery.language.resource;
7 7
8import com.google.common.collect.ImmutableMap;
9import com.google.inject.Inject;
10import com.google.inject.Singleton;
8import org.eclipse.emf.ecore.EObject; 11import org.eclipse.emf.ecore.EObject;
9import org.eclipse.xtext.EcoreUtil2; 12import org.eclipse.xtext.EcoreUtil2;
10import org.eclipse.xtext.naming.IQualifiedNameConverter; 13import org.eclipse.xtext.naming.IQualifiedNameConverter;
@@ -13,19 +16,18 @@ import org.eclipse.xtext.resource.EObjectDescription;
13import org.eclipse.xtext.resource.IEObjectDescription; 16import org.eclipse.xtext.resource.IEObjectDescription;
14import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy; 17import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionStrategy;
15import org.eclipse.xtext.util.IAcceptor; 18import org.eclipse.xtext.util.IAcceptor;
16 19import tools.refinery.language.model.problem.*;
17import com.google.inject.Inject;
18import com.google.inject.Singleton;
19
20import tools.refinery.language.model.problem.NamedElement;
21import tools.refinery.language.model.problem.Node;
22import tools.refinery.language.model.problem.Problem;
23import tools.refinery.language.model.problem.Variable;
24import tools.refinery.language.naming.NamingUtil; 20import tools.refinery.language.naming.NamingUtil;
25import tools.refinery.language.utils.ProblemUtil; 21import tools.refinery.language.utils.ProblemUtil;
26 22
23import java.util.Map;
24
27@Singleton 25@Singleton
28public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy { 26public class ProblemResourceDescriptionStrategy extends DefaultResourceDescriptionStrategy {
27 public static final String ERROR_PREDICATE = "tools.refinery.language.resource" +
28 ".ProblemResourceDescriptionStrategy.ERROR_PREDICATE";
29 public static final String ERROR_PREDICATE_TRUE = "true";
30
29 @Inject 31 @Inject
30 private IQualifiedNameConverter qualifiedNameConverter; 32 private IQualifiedNameConverter qualifiedNameConverter;
31 33
@@ -40,9 +42,10 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
40 } 42 }
41 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); 43 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class);
42 var problemQualifiedName = getNameAsQualifiedName(problem); 44 var problemQualifiedName = getNameAsQualifiedName(problem);
45 var userData = getUserData(eObject);
43 boolean nameExported; 46 boolean nameExported;
44 if (shouldExportSimpleName(eObject)) { 47 if (shouldExportSimpleName(eObject)) {
45 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); 48 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor);
46 nameExported = true; 49 nameExported = true;
47 } else { 50 } else {
48 nameExported = false; 51 nameExported = false;
@@ -56,7 +59,7 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
56 } 59 }
57 qualifiedName = parentQualifiedName.append(qualifiedName); 60 qualifiedName = parentQualifiedName.append(qualifiedName);
58 if (shouldExportSimpleName(parent)) { 61 if (shouldExportSimpleName(parent)) {
59 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); 62 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor);
60 nameExported = true; 63 nameExported = true;
61 } else { 64 } else {
62 nameExported = false; 65 nameExported = false;
@@ -64,16 +67,15 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
64 parent = parent.eContainer(); 67 parent = parent.eContainer();
65 } 68 }
66 if (!nameExported) { 69 if (!nameExported) {
67 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, acceptor); 70 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor);
68 } 71 }
69 return true; 72 return true;
70 } 73 }
71 74
72 protected QualifiedName getNameAsQualifiedName(EObject eObject) { 75 protected QualifiedName getNameAsQualifiedName(EObject eObject) {
73 if (!(eObject instanceof NamedElement)) { 76 if (!(eObject instanceof NamedElement namedElement)) {
74 return null; 77 return null;
75 } 78 }
76 var namedElement = (NamedElement) eObject;
77 var name = namedElement.getName(); 79 var name = namedElement.getName();
78 if (NamingUtil.isNullOrEmpty(name)) { 80 if (NamingUtil.isNullOrEmpty(name)) {
79 return null; 81 return null;
@@ -93,16 +95,28 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
93 return true; 95 return true;
94 } 96 }
95 97
98 protected Map<String, String> getUserData(EObject eObject) {
99 var builder = ImmutableMap.<String, String>builder();
100 if (eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError()) {
101 builder.put(ERROR_PREDICATE, ERROR_PREDICATE_TRUE);
102 }
103 return builder.build();
104 }
105
96 protected boolean shouldExportSimpleName(EObject eObject) { 106 protected boolean shouldExportSimpleName(EObject eObject) {
97 if (eObject instanceof Node node) { 107 if (eObject instanceof Node node) {
98 return !ProblemUtil.isNewNode(node); 108 return !ProblemUtil.isNewNode(node);
99 } 109 }
110 if (eObject instanceof PredicateDefinition predicateDefinition) {
111 return !ProblemUtil.isInvalidMultiplicityConstraint(predicateDefinition);
112 }
100 return true; 113 return true;
101 } 114 }
102 115
103 private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, 116 private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName,
104 IAcceptor<IEObjectDescription> acceptor) { 117 Map<String, String> userData, IAcceptor<IEObjectDescription> acceptor) {
105 var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); 118 var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName);
106 acceptor.accept(EObjectDescription.create(qualifiedNameWithPrefix, eObject)); 119 var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userData);
120 acceptor.accept(description);
107 } 121 }
108} 122}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
index c8c7fd4a..c87fa044 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/BuiltinSymbols.java
@@ -7,7 +7,7 @@ package tools.refinery.language.utils;
7 7
8import tools.refinery.language.model.problem.*; 8import tools.refinery.language.model.problem.*;
9 9
10public record BuiltinSymbols(Problem problem, ClassDeclaration node, ReferenceDeclaration equals, 10public record BuiltinSymbols(Problem problem, ClassDeclaration node, PredicateDefinition equals,
11 PredicateDefinition exists, PredicateDefinition contained, PredicateDefinition contains, 11 PredicateDefinition exists, ClassDeclaration contained, PredicateDefinition contains,
12 PredicateDefinition root) { 12 PredicateDefinition invalidContainer) {
13} 13}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java b/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java
deleted file mode 100644
index e4e4d07a..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/CollectedSymbols.java
+++ /dev/null
@@ -1,15 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8import java.util.Map;
9
10import tools.refinery.language.model.problem.Node;
11import tools.refinery.language.model.problem.Relation;
12
13public record CollectedSymbols(Map<Node, NodeInfo> nodes, Map<Relation, RelationInfo> relations) {
14
15}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java
deleted file mode 100644
index a43c7dfe..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ContainmentRole.java
+++ /dev/null
@@ -1,22 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8import tools.refinery.language.model.problem.PredicateKind;
9
10public enum ContainmentRole {
11 NONE,
12 CONTAINED,
13 CONTAINMENT;
14
15 public static ContainmentRole fromPredicateKind(PredicateKind predicateKind) {
16 return switch (predicateKind) {
17 case CONTAINED -> CONTAINED;
18 case CONTAINMENT -> CONTAINMENT;
19 default -> NONE;
20 };
21 }
22}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java b/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java
deleted file mode 100644
index 0fa7a454..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/NodeInfo.java
+++ /dev/null
@@ -1,9 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8public record NodeInfo(String name, boolean individual) {
9}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
index 738a0896..59e26561 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemDesugarer.java
@@ -6,7 +6,6 @@
6package tools.refinery.language.utils; 6package tools.refinery.language.utils;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Provider;
10import com.google.inject.Singleton; 9import com.google.inject.Singleton;
11import org.eclipse.emf.ecore.EObject; 10import org.eclipse.emf.ecore.EObject;
12import org.eclipse.emf.ecore.resource.Resource; 11import org.eclipse.emf.ecore.resource.Resource;
@@ -21,9 +20,6 @@ public class ProblemDesugarer {
21 @Inject 20 @Inject
22 private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE; 21 private IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE;
23 22
24 @Inject
25 private Provider<SymbolCollector> symbolCollectorProvider;
26
27 public Optional<Problem> getBuiltinProblem(EObject context) { 23 public Optional<Problem> getBuiltinProblem(EObject context) {
28 return Optional.ofNullable(context).map(EObject::eResource).flatMap(resource -> 24 return Optional.ofNullable(context).map(EObject::eResource).flatMap(resource ->
29 cache.get("builtinProblem", resource, () -> doGetBuiltinProblem(resource))); 25 cache.get("builtinProblem", resource, () -> doGetBuiltinProblem(resource)));
@@ -43,12 +39,12 @@ public class ProblemDesugarer {
43 39
44 private BuiltinSymbols doGetBuiltinSymbols(Problem builtin) { 40 private BuiltinSymbols doGetBuiltinSymbols(Problem builtin) {
45 var node = doGetDeclaration(builtin, ClassDeclaration.class, "node"); 41 var node = doGetDeclaration(builtin, ClassDeclaration.class, "node");
46 var equals = doGetEqualsReference(node); 42 var equals = doGetDeclaration(builtin, PredicateDefinition.class, "equals");
47 var exists = doGetDeclaration(builtin, PredicateDefinition.class, "exists"); 43 var exists = doGetDeclaration(builtin, PredicateDefinition.class, "exists");
48 var contained = doGetDeclaration(builtin, PredicateDefinition.class, "contained"); 44 var contained = doGetDeclaration(builtin, ClassDeclaration.class, "contained");
49 var contains = doGetDeclaration(builtin, PredicateDefinition.class, "contains"); 45 var contains = doGetDeclaration(builtin, PredicateDefinition.class, "contains");
50 var root = doGetDeclaration(builtin, PredicateDefinition.class, "root"); 46 var invalidContainer = doGetDeclaration(builtin, PredicateDefinition.class, "invalidContainer");
51 return new BuiltinSymbols(builtin, node, equals, exists, contained, contains, root); 47 return new BuiltinSymbols(builtin, node, equals, exists, contained, contains, invalidContainer);
52 } 48 }
53 49
54 private <T extends Statement & NamedElement> T doGetDeclaration(Problem builtin, Class<T> type, String name) { 50 private <T extends Statement & NamedElement> T doGetDeclaration(Problem builtin, Class<T> type, String name) {
@@ -57,13 +53,6 @@ public class ProblemDesugarer {
57 .orElseThrow(() -> new IllegalArgumentException("Built-in declaration " + name + " was not found")); 53 .orElseThrow(() -> new IllegalArgumentException("Built-in declaration " + name + " was not found"));
58 } 54 }
59 55
60 private ReferenceDeclaration doGetEqualsReference(ClassDeclaration nodeClassDeclaration) {
61 return (ReferenceDeclaration) nodeClassDeclaration.getFeatureDeclarations().stream()
62 .filter(reference -> reference instanceof ReferenceDeclaration &&
63 "equals".equals(reference.getName())).findFirst()
64 .orElseThrow(() -> new IllegalArgumentException("Reference " + "equals" + " not found"));
65 }
66
67 public Collection<ClassDeclaration> getSuperclassesAndSelf(ClassDeclaration classDeclaration) { 56 public Collection<ClassDeclaration> getSuperclassesAndSelf(ClassDeclaration classDeclaration) {
68 return cache.get(Tuples.create(classDeclaration, "superclassesAndSelf"), classDeclaration.eResource(), 57 return cache.get(Tuples.create(classDeclaration, "superclassesAndSelf"), classDeclaration.eResource(),
69 () -> doGetSuperclassesAndSelf(classDeclaration)); 58 () -> doGetSuperclassesAndSelf(classDeclaration));
@@ -109,9 +98,4 @@ public class ProblemDesugarer {
109 public boolean isContainmentReference(ReferenceDeclaration referenceDeclaration) { 98 public boolean isContainmentReference(ReferenceDeclaration referenceDeclaration) {
110 return referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT; 99 return referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT;
111 } 100 }
112
113 public CollectedSymbols collectSymbols(Problem problem) {
114 return cache.get(Tuples.create(problem, "collectedSymbols"), problem.eResource(),
115 () -> symbolCollectorProvider.get().collectSymbols(problem));
116 }
117} 101}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
index 9486dc2a..a9efc4bb 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java
@@ -7,33 +7,12 @@ package tools.refinery.language.utils;
7 7
8import org.eclipse.emf.common.util.URI; 8import org.eclipse.emf.common.util.URI;
9import org.eclipse.emf.ecore.EObject; 9import org.eclipse.emf.ecore.EObject;
10 10import tools.refinery.language.model.problem.*;
11import tools.refinery.language.model.problem.ImplicitVariable;
12import tools.refinery.language.model.problem.Node;
13import tools.refinery.language.model.problem.ProblemPackage;
14import tools.refinery.language.model.problem.Variable;
15 11
16public final class ProblemUtil { 12public final class ProblemUtil {
17 public static final String BUILTIN_LIBRARY_NAME = "builtin"; 13 public static final String BUILTIN_LIBRARY_NAME = "builtin";
18
19 public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(BUILTIN_LIBRARY_NAME); 14 public static final URI BUILTIN_LIBRARY_URI = getLibraryUri(BUILTIN_LIBRARY_NAME);
20 15
21 public static final String NODE_CLASS_NAME = "node";
22
23 public static final String DOMAIN_CLASS_NAME = "domain";
24
25 public static final String DATA_CLASS_NAME = "data";
26
27 public static final String INT_CLASS_NAME = "int";
28
29 public static final String REAL_CLASS_NAME = "real";
30
31 public static final String STRING_CLASS_NAME = "string";
32
33 public static final String EQUALS_RELATION_NAME = "equals";
34
35 public static final String EXISTS_PREDICATE_NAME = "exists";
36
37 private ProblemUtil() { 16 private ProblemUtil() {
38 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
39 } 18 }
@@ -70,6 +49,10 @@ public final class ProblemUtil {
70 } 49 }
71 } 50 }
72 51
52 public static boolean isError(EObject eObject) {
53 return eObject instanceof PredicateDefinition predicateDefinition && predicateDefinition.isError();
54 }
55
73 public static boolean isIndividualNode(Node node) { 56 public static boolean isIndividualNode(Node node) {
74 var containingFeature = node.eContainingFeature(); 57 var containingFeature = node.eContainingFeature();
75 return containingFeature == ProblemPackage.Literals.INDIVIDUAL_DECLARATION__NODES 58 return containingFeature == ProblemPackage.Literals.INDIVIDUAL_DECLARATION__NODES
@@ -80,6 +63,26 @@ public final class ProblemUtil {
80 return node.eContainingFeature() == ProblemPackage.Literals.CLASS_DECLARATION__NEW_NODE; 63 return node.eContainingFeature() == ProblemPackage.Literals.CLASS_DECLARATION__NEW_NODE;
81 } 64 }
82 65
66 public static boolean isInvalidMultiplicityConstraint(PredicateDefinition predicateDefinition) {
67 return predicateDefinition.eContainingFeature() ==
68 ProblemPackage.Literals.REFERENCE_DECLARATION__INVALID_MULTIPLICITY;
69 }
70
71 public static boolean hasMultiplicityConstraint(ReferenceDeclaration referenceDeclaration) {
72 var opposite = referenceDeclaration.getOpposite();
73 if (opposite != null && opposite.getKind() == ReferenceKind.CONTAINMENT) {
74 return false;
75 }
76 var multiplicity = referenceDeclaration.getMultiplicity();
77 if (multiplicity instanceof UnboundedMultiplicity) {
78 return false;
79 }
80 if (multiplicity instanceof RangeMultiplicity rangeMultiplicity) {
81 return rangeMultiplicity.getLowerBound() > 0 || rangeMultiplicity.getUpperBound() >= 0;
82 }
83 return true;
84 }
85
83 private static URI getLibraryUri(String libraryName) { 86 private static URI getLibraryUri(String libraryName) {
84 return URI.createURI(ProblemUtil.class.getClassLoader() 87 return URI.createURI(ProblemUtil.class.getClassLoader()
85 .getResource("tools/refinery/language/%s.problem".formatted(libraryName)).toString()); 88 .getResource("tools/refinery/language/%s.problem".formatted(libraryName)).toString());
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java b/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java
deleted file mode 100644
index 1c46fe72..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/RelationInfo.java
+++ /dev/null
@@ -1,29 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8import tools.refinery.language.model.problem.*;
9
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.List;
13
14public record RelationInfo(String name, ContainmentRole containmentRole, List<Parameter> parameters,
15 Multiplicity multiplicity, Relation opposite, Collection<Conjunction> bodies,
16 Collection<Assertion> assertions, Collection<TypeScope> typeScopes) {
17 public RelationInfo(String name, ContainmentRole containmentRole, List<Parameter> parameters,
18 Multiplicity multiplicity, Relation opposite, Collection<Conjunction> bodies) {
19 this(name, containmentRole, parameters, multiplicity, opposite, bodies, new ArrayList<>(), new ArrayList<>());
20 }
21
22 public boolean hasDefinition() {
23 return bodies != null && !bodies.isEmpty();
24 }
25
26 public int arity() {
27 return parameters.size();
28 }
29}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java b/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java
deleted file mode 100644
index a4ea1113..00000000
--- a/subprojects/language/src/main/java/tools/refinery/language/utils/SymbolCollector.java
+++ /dev/null
@@ -1,255 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.utils;
7
8import com.google.inject.Inject;
9import org.eclipse.emf.ecore.EObject;
10import org.eclipse.xtext.naming.IQualifiedNameConverter;
11import org.eclipse.xtext.naming.IQualifiedNameProvider;
12import tools.refinery.language.model.problem.*;
13
14import java.util.LinkedHashMap;
15import java.util.List;
16import java.util.Map;
17
18class SymbolCollector {
19 @Inject
20 private IQualifiedNameProvider qualifiedNameProvider;
21
22 @Inject
23 private IQualifiedNameConverter qualifiedNameConverter;
24
25 @Inject
26 private ProblemDesugarer desugarer;
27
28 private BuiltinSymbols builtinSymbols;
29
30 private final Map<Node, NodeInfo> nodes = new LinkedHashMap<>();
31
32 private final Map<Relation, RelationInfo> relations = new LinkedHashMap<>();
33
34 public CollectedSymbols collectSymbols(Problem problem) {
35 builtinSymbols = desugarer.getBuiltinSymbols(problem).orElseThrow(() -> new IllegalArgumentException(
36 "Problem has no associated built-in library"));
37 collectOwnSymbols(builtinSymbols.problem());
38 collectOwnSymbols(problem);
39 return new CollectedSymbols(nodes, relations);
40 }
41
42 public void collectOwnSymbols(Problem problem) {
43 collectOwnRelations(problem);
44 collectOwnNodes(problem);
45 collectOwnAssertions(problem);
46 }
47
48 private void collectOwnRelations(Problem problem) {
49 for (var statement : problem.getStatements()) {
50 if (statement instanceof PredicateDefinition predicateDefinition) {
51 collectPredicate(predicateDefinition);
52 } else if (statement instanceof ClassDeclaration classDeclaration) {
53 collectClass(classDeclaration);
54 } else if (statement instanceof EnumDeclaration enumDeclaration) {
55 collectEnum(enumDeclaration);
56 } else if (statement instanceof RuleDefinition) {
57 throw new UnsupportedOperationException("Rules are not currently supported");
58 }
59 }
60 }
61
62 private void collectPredicate(PredicateDefinition predicateDefinition) {
63 var predicateKind = predicateDefinition.getKind();
64 var info = new RelationInfo(getQualifiedNameString(predicateDefinition),
65 ContainmentRole.fromPredicateKind(predicateKind), predicateDefinition.getParameters(), null, null,
66 predicateDefinition.getBodies());
67 relations.put(predicateDefinition, info);
68 }
69
70 private void collectClass(ClassDeclaration classDeclaration) {
71 var contained = classDeclaration != builtinSymbols.node();
72 var containmentRole = contained ? ContainmentRole.CONTAINED : ContainmentRole.NONE;
73 var instanceParameter = ProblemFactory.eINSTANCE.createParameter();
74 instanceParameter.setName("instance");
75 var classInfo = new RelationInfo(getQualifiedNameString(classDeclaration), containmentRole,
76 List.of(instanceParameter), null, null, List.of());
77 relations.put(classDeclaration, classInfo);
78 collectFeatures(classDeclaration);
79 }
80
81 private void collectFeatures(ClassDeclaration classDeclaration) {
82 for (var featureDeclaration : classDeclaration.getFeatureDeclarations()) {
83 if (featureDeclaration instanceof ReferenceDeclaration referenceDeclaration) {
84 collectReference(classDeclaration, referenceDeclaration);
85 } else if (featureDeclaration instanceof AttributeDeclaration attributeDeclaration) {
86 collectAttribute(classDeclaration, attributeDeclaration);
87 } else if (featureDeclaration instanceof FlagDeclaration flagDeclaration) {
88 collectFlag(classDeclaration, flagDeclaration);
89 } else {
90 throw new IllegalArgumentException("Unknown FeatureDeclaration: " + featureDeclaration);
91 }
92 }
93 }
94
95 private void collectReference(ClassDeclaration classDeclaration, ReferenceDeclaration referenceDeclaration) {
96 var referenceRole = desugarer.isContainmentReference(referenceDeclaration) ?
97 ContainmentRole.CONTAINMENT :
98 ContainmentRole.NONE;
99 var sourceParameter = ProblemFactory.eINSTANCE.createParameter();
100 sourceParameter.setName("source");
101 sourceParameter.setParameterType(classDeclaration);
102 var targetParameter = ProblemFactory.eINSTANCE.createParameter();
103 targetParameter.setName("target");
104 var multiplicity = referenceDeclaration.getMultiplicity();
105 if (multiplicity == null) {
106 var exactMultiplicity = ProblemFactory.eINSTANCE.createExactMultiplicity();
107 exactMultiplicity.setExactValue(1);
108 multiplicity = exactMultiplicity;
109 }
110 targetParameter.setParameterType(referenceDeclaration.getReferenceType());
111 var referenceInfo = new RelationInfo(getQualifiedNameString(referenceDeclaration), referenceRole,
112 List.of(sourceParameter, targetParameter), multiplicity, referenceDeclaration.getOpposite(),
113 List.of());
114 this.relations.put(referenceDeclaration, referenceInfo);
115 }
116
117 private void collectAttribute(ClassDeclaration classDeclaration, AttributeDeclaration attributeDeclaration) {
118 // TODO Implement attribute handling.
119 }
120
121 private void collectFlag(ClassDeclaration classDeclaration, FlagDeclaration flagDeclaration) {
122 var parameter = ProblemFactory.eINSTANCE.createParameter();
123 parameter.setName("object");
124 parameter.setParameterType(classDeclaration);
125 var referenceInfo = new RelationInfo(getQualifiedNameString(flagDeclaration), ContainmentRole.NONE,
126 List.of(parameter), null, null, List.of());
127 this.relations.put(flagDeclaration, referenceInfo);
128 }
129
130 private void collectEnum(EnumDeclaration enumDeclaration) {
131 var instanceParameter = ProblemFactory.eINSTANCE.createParameter();
132 instanceParameter.setName("instance");
133 var info = new RelationInfo(getQualifiedNameString(enumDeclaration), ContainmentRole.NONE,
134 List.of(instanceParameter), null, null, List.of());
135 this.relations.put(enumDeclaration, info);
136 }
137
138 private void collectOwnNodes(Problem problem) {
139 for (var statement : problem.getStatements()) {
140 if (statement instanceof IndividualDeclaration individualDeclaration) {
141 collectIndividuals(individualDeclaration);
142 } else if (statement instanceof ClassDeclaration classDeclaration) {
143 collectNewNode(classDeclaration);
144 } else if (statement instanceof EnumDeclaration enumDeclaration) {
145 collectEnumLiterals(enumDeclaration);
146 }
147 }
148 for (var node : problem.getNodes()) {
149 addNode(node, false);
150 }
151 }
152
153 private void collectIndividuals(IndividualDeclaration individualDeclaration) {
154 for (var individual : individualDeclaration.getNodes()) {
155 addNode(individual, true);
156 }
157 }
158
159 private void collectNewNode(ClassDeclaration classDeclaration) {
160 var newNode = classDeclaration.getNewNode();
161 if (newNode != null) {
162 addNode(newNode, false);
163 }
164 }
165
166 private void collectEnumLiterals(EnumDeclaration enumDeclaration) {
167 for (var literal : enumDeclaration.getLiterals()) {
168 addNode(literal, true);
169 }
170 }
171
172 private void addNode(Node node, boolean individual) {
173 var info = new NodeInfo(getQualifiedNameString(node), individual);
174 this.nodes.put(node, info);
175 }
176
177 private String getQualifiedNameString(EObject eObject) {
178 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(eObject);
179 if (qualifiedName == null) {
180 return null;
181 }
182 return qualifiedNameConverter.toString(qualifiedName);
183 }
184
185 private void collectOwnAssertions(Problem problem) {
186 for (var statement : problem.getStatements()) {
187 if (statement instanceof Assertion assertion) {
188 collectAssertion(assertion);
189 } else if (statement instanceof PredicateDefinition predicateDefinition) {
190 collectPredicateAssertion(predicateDefinition);
191 } else if (statement instanceof ClassDeclaration classDeclaration) {
192 collectClassAssertion(classDeclaration);
193 } else if (statement instanceof EnumDeclaration enumDeclaration) {
194 collectEnumAssertions(enumDeclaration);
195 }
196 }
197 }
198
199 private void collectAssertion(Assertion assertion) {
200 var relationInfo = this.relations.get(assertion.getRelation());
201 if (relationInfo == null) {
202 throw new IllegalStateException("Assertion refers to unknown relation");
203 }
204 if (assertion.getArguments().size() != relationInfo.parameters().size()) {
205 // Silently ignoring assertions of invalid arity helps when SymbolCollector is called on an invalid
206 // Problem during editing. The errors can still be detected by the Problem validator.
207 return;
208 }
209 relationInfo.assertions().add(assertion);
210 }
211
212 private void collectPredicateAssertion(PredicateDefinition predicateDefinition) {
213 if (predicateDefinition.getKind() != PredicateKind.ERROR) {
214 return;
215 }
216 int arity = predicateDefinition.getParameters().size();
217 addAssertion(predicateDefinition, LogicValue.FALSE, new Node[arity]);
218 }
219
220 private void collectClassAssertion(ClassDeclaration classDeclaration) {
221 var node = classDeclaration.getNewNode();
222 if (node == null) {
223 return;
224 }
225 addAssertion(classDeclaration, LogicValue.TRUE, node);
226 addAssertion(builtinSymbols.exists(), LogicValue.UNKNOWN, node);
227 addAssertion(builtinSymbols.equals(), LogicValue.UNKNOWN, node, node);
228 }
229
230 private void collectEnumAssertions(EnumDeclaration enumDeclaration) {
231 for (var literal : enumDeclaration.getLiterals()) {
232 addAssertion(enumDeclaration, LogicValue.TRUE, literal);
233 }
234 }
235
236 private void addAssertion(Relation relation, LogicValue logicValue, Node... nodes) {
237 var assertion = ProblemFactory.eINSTANCE.createAssertion();
238 assertion.setRelation(relation);
239 for (var node : nodes) {
240 AssertionArgument argument;
241 if (node == null) {
242 argument = ProblemFactory.eINSTANCE.createWildcardAssertionArgument();
243 } else {
244 var nodeArgument = ProblemFactory.eINSTANCE.createNodeAssertionArgument();
245 nodeArgument.setNode(node);
246 argument = nodeArgument;
247 }
248 assertion.getArguments().add(argument);
249 }
250 var value = ProblemFactory.eINSTANCE.createLogicConstant();
251 value.setLogicValue(logicValue);
252 assertion.setValue(value);
253 collectAssertion(assertion);
254 }
255}
diff --git a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem
index 9c1d7669..022c3167 100644
--- a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem
+++ b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem
@@ -3,43 +3,14 @@
3% SPDX-License-Identifier: EPL-2.0 3% SPDX-License-Identifier: EPL-2.0
4problem builtin. 4problem builtin.
5 5
6abstract class node { 6abstract class node.
7 refers node[] equals opposite equals
8}
9 7
10pred exists(node node). 8pred exists(node).
11 9
12% class Integer { 10pred equals(left, right).
13% int intValue
14% }
15%
16% class Real {
17% real realValue
18% }
19%
20% class String {
21% string stringValue
22% }
23%
24% enum Boolean {
25% TRUE, FALSE
26% }
27 11
28pred contained(node node). 12abstract class contained extends node.
29 13
30pred contains(node container, node contained). 14pred contains(container, contained contained).
31 15
32pred root(node node). 16error invalidContainer(contained contained).
33
34% error missingContainer(contained node) <->
35% !contains(node, _), !root(node).
36%
37% error tooManyContainers(contained node) <->
38% #contains(_, node) > 1
39% ;
40% contains(_, node), root(node)
41% ;
42% contains(_, node), !contained(node).
43%
44% error containmentCycle(node node) <->
45% contains+(node, node).
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java
index c7952369..96e7cf9c 100644
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java
+++ b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java
@@ -31,7 +31,6 @@ class ProblemParsingTest {
31 class Person { 31 class Person {
32 Person[0..*] children opposite parent 32 Person[0..*] children opposite parent
33 Person[0..1] parent opposite children 33 Person[0..1] parent opposite children
34 int age
35 TaxStatus taxStatus 34 TaxStatus taxStatus
36 } 35 }
37 36
@@ -51,8 +50,6 @@ class ProblemParsingTest {
51 children(anne, ciri). 50 children(anne, ciri).
52 ?children(bob, ciri). 51 ?children(bob, ciri).
53 taxStatus(anne, ADULT). 52 taxStatus(anne, ADULT).
54 age(bob): 21..35.
55 age(ciri): 10.
56 """); 53 """);
57 assertThat(problem.errors(), empty()); 54 assertThat(problem.errors(), empty());
58 } 55 }
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java
index 1180d131..ed193e90 100644
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java
+++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java
@@ -29,9 +29,7 @@ class TransitiveClosureParserTest {
29 @Test 29 @Test
30 void binaryAddOperatorTest() { 30 void binaryAddOperatorTest() {
31 var problem = parseHelper.parse(""" 31 var problem = parseHelper.parse("""
32 fn int a(). 32 pred foo(a, b) <-> a + (b) > 10.
33 fn int b().
34 pred foo() <-> a() + (b()) > 10.
35 """); 33 """);
36 assertThat(problem.errors(), empty()); 34 assertThat(problem.errors(), empty());
37 var literal = problem.pred("foo").conj(0).lit(0).get(); 35 var literal = problem.pred("foo").conj(0).lit(0).get();
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
index 3f3a081f..9f4f2bbf 100644
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
+++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java
@@ -178,7 +178,7 @@ class ProblemSerializerTest {
178 var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); 178 var variable = ProblemFactory.eINSTANCE.createImplicitVariable();
179 variable.setName("q"); 179 variable.setName("q");
180 conjunction.getImplicitVariables().add(variable); 180 conjunction.getImplicitVariables().add(variable);
181 var equals = nodeType.feature("equals"); 181 var equals = builtin.pred("equals").get();
182 conjunction.getLiterals().add(createAtom(equals, parameter1, variable)); 182 conjunction.getLiterals().add(createAtom(equals, parameter1, variable));
183 conjunction.getLiterals().add(createAtom(equals, variable, parameter2)); 183 conjunction.getLiterals().add(createAtom(equals, variable, parameter2));
184 pred.getBodies().add(conjunction); 184 pred.getBodies().add(conjunction);
@@ -212,7 +212,7 @@ class ProblemSerializerTest {
212 pred.getParameters().add(parameter); 212 pred.getParameters().add(parameter);
213 var conjunction = ProblemFactory.eINSTANCE.createConjunction(); 213 var conjunction = ProblemFactory.eINSTANCE.createConjunction();
214 var atom = ProblemFactory.eINSTANCE.createAtom(); 214 var atom = ProblemFactory.eINSTANCE.createAtom();
215 var equals = nodeType.feature("equals"); 215 var equals = builtin.pred("equals").get();
216 atom.setRelation(equals); 216 atom.setRelation(equals);
217 var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr(); 217 var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr();
218 arg1.setVariableOrNode(parameter); 218 arg1.setVariableOrNode(parameter);
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java
deleted file mode 100644
index d200eeff..00000000
--- a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.tests.utils;
7
8import com.google.inject.Inject;
9import org.eclipse.xtext.testing.InjectWith;
10import org.eclipse.xtext.testing.extensions.InjectionExtension;
11import org.hamcrest.Matcher;
12import org.junit.jupiter.api.Disabled;
13import org.junit.jupiter.api.Test;
14import org.junit.jupiter.api.extension.ExtendWith;
15import org.junit.jupiter.params.ParameterizedTest;
16import org.junit.jupiter.params.provider.Arguments;
17import org.junit.jupiter.params.provider.MethodSource;
18import tools.refinery.language.model.problem.*;
19import tools.refinery.language.model.tests.utils.ProblemParseHelper;
20import tools.refinery.language.tests.ProblemInjectorProvider;
21import tools.refinery.language.utils.ContainmentRole;
22import tools.refinery.language.utils.ProblemDesugarer;
23
24import java.util.stream.Stream;
25
26import static org.hamcrest.MatcherAssert.assertThat;
27import static org.hamcrest.Matchers.*;
28import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
29
30@ExtendWith(InjectionExtension.class)
31@InjectWith(ProblemInjectorProvider.class)
32class SymbolCollectorTest {
33 @Inject
34 private ProblemParseHelper parseHelper;
35
36 @Inject
37 private ProblemDesugarer desugarer;
38
39 @Test
40 void implicitNodeTest() {
41 var problem = parseHelper.parse("""
42 exists(a).
43 """);
44 var collectedSymbols = desugarer.collectSymbols(problem.get());
45 var node = problem.node("a");
46 assertThat(collectedSymbols.nodes(), hasKey(node));
47 assertThat(collectedSymbols.nodes().get(node).individual(), is(false));
48 }
49
50 @Test
51 void individualNodeTest() {
52 var problem = parseHelper.parse("""
53 indiv a.
54 """);
55 var collectedSymbols = desugarer.collectSymbols(problem.get());
56 var node = problem.individualNode("a");
57 assertThat(collectedSymbols.nodes(), hasKey(node));
58 assertThat(collectedSymbols.nodes().get(node).individual(), is(true));
59 }
60
61 @Test
62 void classTest() {
63 var problem = parseHelper.parse("""
64 class Foo.
65 """);
66 var collectedSymbols = desugarer.collectSymbols(problem.get());
67 var classDeclaration = problem.findClass("Foo").get();
68 assertThat(collectedSymbols.relations(), hasKey(classDeclaration));
69 var classInfo = collectedSymbols.relations().get(classDeclaration);
70 assertThat(classInfo.parameters(), hasSize(1));
71 assertThat(classInfo.containmentRole(), is(ContainmentRole.CONTAINED));
72 assertThat(classInfo.hasDefinition(), is(false));
73 var newNode = classDeclaration.getNewNode();
74 assertThat(collectedSymbols.nodes(), hasKey(newNode));
75 assertThat(collectedSymbols.nodes().get(newNode).individual(), is(false));
76 assertThat(classInfo.assertions(), assertsNode(newNode, LogicValue.TRUE));
77 assertThat(collectedSymbols.relations().get(problem.builtinSymbols().exists()).assertions(),
78 assertsNode(newNode, LogicValue.UNKNOWN));
79 assertThat(collectedSymbols.relations().get(problem.builtinSymbols().equals()).assertions(),
80 assertsNode(newNode, LogicValue.UNKNOWN));
81 }
82
83 @Test
84 void abstractClassTest() {
85 var problem = parseHelper.parse("""
86 abstract class Foo.
87 """);
88 var collectedSymbols = desugarer.collectSymbols(problem.get());
89 assertThat(collectedSymbols.relations().get(problem.findClass("Foo").get()).assertions(), hasSize(0));
90 }
91
92 @Test
93 void referenceTest() {
94 var problem = parseHelper.parse("""
95 class Foo {
96 refers Foo[] bar opposite quux
97 refers Foo quux opposite bar
98 }
99 """);
100 var collectedSymbols = desugarer.collectSymbols(problem.get());
101 var fooClass = problem.findClass("Foo");
102 var barReference = fooClass.feature("bar");
103 var barInfo = collectedSymbols.relations().get(barReference);
104 var quuxReference = fooClass.feature("quux");
105 var quuxInfo = collectedSymbols.relations().get(quuxReference);
106 assertThat(barInfo.containmentRole(), is(ContainmentRole.NONE));
107 assertThat(barInfo.opposite(), is(quuxReference));
108 assertThat(barInfo.multiplicity(), is(instanceOf(UnboundedMultiplicity.class)));
109 assertThat(barInfo.hasDefinition(), is(false));
110 assertThat(quuxInfo.containmentRole(), is(ContainmentRole.NONE));
111 assertThat(quuxInfo.opposite(), is(barReference));
112 assertThat(quuxInfo.multiplicity(), is(instanceOf(ExactMultiplicity.class)));
113 assertThat(quuxInfo.multiplicity(), hasProperty("exactValue", is(1)));
114 assertThat(quuxInfo.hasDefinition(), is(false));
115 }
116
117 @Test
118 void containmentReferenceTest() {
119 var problem = parseHelper.parse("""
120 class Foo {
121 contains Foo[] bar
122 }
123 """);
124 var collectedSymbols = desugarer.collectSymbols(problem.get());
125 assertThat(collectedSymbols.relations().get(problem.findClass("Foo").feature("bar")).containmentRole(),
126 is(ContainmentRole.CONTAINMENT));
127 }
128
129 @Disabled("TODO: Rework numerical references")
130 @Test
131 void dataReferenceTest() {
132 var problem = parseHelper.parse("""
133 class Foo {
134 int bar
135 }
136 """);
137 var collectedSymbols = desugarer.collectSymbols(problem.get());
138 assertThat(collectedSymbols.relations().get(problem.findClass("Foo").feature("bar")).containmentRole(),
139 is(ContainmentRole.CONTAINMENT));
140 }
141
142 @Test
143 void enumTest() {
144 var problem = parseHelper.parse("""
145 enum Foo {
146 bar, quux
147 }
148 """);
149 var collectedSymbols = desugarer.collectSymbols(problem.get());
150 var enumDeclaration = problem.findEnum("Foo");
151 var enumInfo = collectedSymbols.relations().get(enumDeclaration.get());
152 assertThat(enumInfo.containmentRole(), is(ContainmentRole.NONE));
153 assertThat(enumInfo.assertions(), assertsNode(enumDeclaration.literal("bar"), LogicValue.TRUE));
154 assertThat(enumInfo.assertions(), assertsNode(enumDeclaration.literal("quux"), LogicValue.TRUE));
155 }
156
157 @ParameterizedTest
158 @MethodSource
159 void predicateTest(String keyword, ContainmentRole containmentRole) {
160 var problem = parseHelper.parse(keyword + " foo(node x) <-> domain(x); data(x).");
161 var collectedSymbols = desugarer.collectSymbols(problem.get());
162 var predicateInfo = collectedSymbols.relations().get(problem.pred("foo").get());
163 assertThat(predicateInfo.containmentRole(), is(containmentRole));
164 assertThat(predicateInfo.parameters(), hasSize(1));
165 assertThat(predicateInfo.bodies(), hasSize(2));
166 assertThat(predicateInfo.hasDefinition(), is(true));
167 }
168
169 static Stream<Arguments> predicateTest() {
170 return Stream.of(Arguments.of("pred", ContainmentRole.NONE), Arguments.of("error", ContainmentRole.NONE),
171 Arguments.of("contained pred", ContainmentRole.CONTAINED), Arguments.of("containment pred",
172 ContainmentRole.CONTAINMENT));
173 }
174
175 @ParameterizedTest
176 @MethodSource("logicValues")
177 void assertionTest(String keyword, LogicValue value) {
178 var problem = parseHelper.parse("""
179 pred foo(node x).
180 foo(a): %s.
181 """.formatted(keyword));
182 var collectedSymbols = desugarer.collectSymbols(problem.get());
183 assertThat(collectedSymbols.relations().get(problem.pred("foo").get()).assertions(),
184 assertsNode(problem.node("a"), value));
185 }
186
187 @ParameterizedTest
188 @MethodSource("logicValues")
189 void defaultAssertionTest(String keyword, LogicValue value) {
190 var problem = parseHelper.parse("""
191 pred foo(node x).
192 default foo(a): %s.
193 """.formatted(keyword));
194 var collectedSymbols = desugarer.collectSymbols(problem.get());
195 assertThat(collectedSymbols.relations().get(problem.pred("foo").get()).assertions(),
196 assertsNode(problem.node("a"), value));
197 }
198
199 static Stream<Arguments> logicValues() {
200 return Stream.of(Arguments.of("true", LogicValue.TRUE), Arguments.of("false", LogicValue.FALSE),
201 Arguments.of("unknown", LogicValue.UNKNOWN), Arguments.of("error", LogicValue.ERROR));
202 }
203
204 @Test
205 void invalidAssertionArityTest() {
206 var problem = parseHelper.parse("""
207 pred foo(node x).
208 foo(a, b).
209 """);
210 var collectedSymbols = desugarer.collectSymbols(problem.get());
211 assertThat(collectedSymbols.relations().get(problem.pred("foo").get()).assertions(), hasSize(0));
212 }
213
214 @Test
215 void invalidProblemTest() {
216 var problem = parseHelper.parse("""
217 class Foo {
218 bar[] opposite quux
219 Foo quux opposite bar
220 }
221 """).get();
222 assertDoesNotThrow(() -> desugarer.collectSymbols(problem));
223 }
224
225 @Test
226 void errorAssertionTest() {
227 var problem = parseHelper.parse("""
228 error foo(node a, node b) <-> equals(a, b).
229 """);
230 var collectedSymbols = desugarer.collectSymbols(problem.get());
231 var fooInfo = collectedSymbols.relations().get(problem.pred("foo").get());
232 assertThat(fooInfo.assertions(), hasSize(1));
233 var assertion = fooInfo.assertions().stream().findFirst().orElseThrow();
234 assertThat(assertion.getValue(), hasProperty("logicValue", is(LogicValue.FALSE)));
235 assertThat(assertion.getArguments(), hasSize(2));
236 assertThat(assertion.getArguments(), everyItem(instanceOf(WildcardAssertionArgument.class)));
237 }
238
239 private static Matcher<Iterable<? super Assertion>> assertsNode(Node node, LogicValue value) {
240 return hasItem(allOf(hasProperty("arguments", hasItem(hasProperty("node", is(node)))), hasProperty("value",
241 hasProperty("logicValue", is(value)))));
242 }
243}
diff --git a/subprojects/visualization/build.gradle.kts b/subprojects/store-dse-visualization/build.gradle.kts
index abad0491..abad0491 100644
--- a/subprojects/visualization/build.gradle.kts
+++ b/subprojects/store-dse-visualization/build.gradle.kts
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java
index ad80bbc6..ad80bbc6 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerAdapter.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java
index 592f5fcf..592f5fcf 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerBuilder.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java
index 46663b2a..46663b2a 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/ModelVisualizerStoreAdapter.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java
index c5dffeb2..c5dffeb2 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/FileFormat.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java
index 04be22d6..04be22d6 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizeStoreAdapterImpl.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java
index 031b2e6b..031b2e6b 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerAdapterImpl.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java
index e4d801d8..e4d801d8 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/internal/ModelVisualizerBuilderImpl.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java
index 414ef737..414ef737 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/VisualizationStore.java
diff --git a/subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java
index 08a69cb4..08a69cb4 100644
--- a/subprojects/visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java
+++ b/subprojects/store-dse-visualization/src/main/java/tools/refinery/visualization/statespace/internal/VisualizationStoreImpl.java
diff --git a/subprojects/store-dse/build.gradle.kts b/subprojects/store-dse/build.gradle.kts
index bb1dee7d..5517e189 100644
--- a/subprojects/store-dse/build.gradle.kts
+++ b/subprojects/store-dse/build.gradle.kts
@@ -10,7 +10,8 @@ plugins {
10 10
11dependencies { 11dependencies {
12 api(project(":refinery-store-query")) 12 api(project(":refinery-store-query"))
13 api(project(":refinery-store-query-viatra")) 13 implementation(project(":refinery-store-dse-visualization"))
14 api(project(":refinery-store-reasoning")) 14 implementation(libs.eclipseCollections.api)
15 api(project(":refinery-visualization")) 15 runtimeOnly(libs.eclipseCollections)
16 testImplementation(project(":refinery-store-query-viatra"))
16} 17}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java
deleted file mode 100644
index 524c2f55..00000000
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/ActionFactory.java
+++ /dev/null
@@ -1,14 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import org.eclipse.collections.api.block.procedure.Procedure;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.tuple.Tuple;
11
12public interface ActionFactory {
13 Procedure<Tuple> prepare(Model model);
14}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/DanglingEdges.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/DanglingEdges.java
new file mode 100644
index 00000000..ac9d125b
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/DanglingEdges.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification;
7
8public enum DanglingEdges {
9 IGNORE,
10 DELETE,
11 FAIL
12}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java
index f15c16e0..58b60499 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/ModificationAdapter.java
@@ -16,7 +16,7 @@ public interface ModificationAdapter extends ModelAdapter {
16 16
17 Tuple1 createObject(); 17 Tuple1 createObject();
18 18
19 Tuple deleteObject(Tuple tuple); 19 boolean deleteObject(Tuple tuple, DanglingEdges danglingEdges);
20 20
21 static ModificationBuilder builder() { 21 static ModificationBuilder builder() {
22 return new ModificationBuilderImpl(); 22 return new ModificationBuilderImpl();
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/CreateActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/CreateActionLiteral.java
new file mode 100644
index 00000000..5b86a5e1
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/CreateActionLiteral.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.actions;
7
8import tools.refinery.store.dse.modification.ModificationAdapter;
9import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
10import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
11import tools.refinery.store.model.Model;
12import tools.refinery.store.query.term.NodeVariable;
13
14import java.util.List;
15
16public class CreateActionLiteral extends AbstractActionLiteral {
17 private final NodeVariable variable;
18
19 public CreateActionLiteral(NodeVariable variable) {
20
21 this.variable = variable;
22 }
23
24 public NodeVariable getVariable() {
25 return variable;
26 }
27
28 @Override
29 public List<NodeVariable> getInputVariables() {
30 return List.of();
31 }
32
33 @Override
34 public List<NodeVariable> getOutputVariables() {
35 return List.of(variable);
36 }
37
38 @Override
39 public BoundActionLiteral bindToModel(Model model) {
40 var adapter = model.getAdapter(ModificationAdapter.class);
41 return ignoredTuple -> adapter.createObject();
42 }
43}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/DeleteActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/DeleteActionLiteral.java
new file mode 100644
index 00000000..18ad2b9d
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/DeleteActionLiteral.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.actions;
7
8import tools.refinery.store.dse.modification.DanglingEdges;
9import tools.refinery.store.dse.modification.ModificationAdapter;
10import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
11import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
12import tools.refinery.store.model.Model;
13import tools.refinery.store.query.term.NodeVariable;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.List;
17
18public class DeleteActionLiteral extends AbstractActionLiteral {
19 private final NodeVariable variable;
20 private final DanglingEdges danglingEdges;
21
22 public DeleteActionLiteral(NodeVariable variable, DanglingEdges danglingEdges) {
23
24 this.variable = variable;
25 this.danglingEdges = danglingEdges;
26 }
27
28 public NodeVariable getVariable() {
29 return variable;
30 }
31
32 public DanglingEdges getDanglingEdges() {
33 return danglingEdges;
34 }
35
36 @Override
37 public List<NodeVariable> getInputVariables() {
38 return List.of(variable);
39 }
40
41 @Override
42 public List<NodeVariable> getOutputVariables() {
43 return List.of();
44 }
45
46 @Override
47 public BoundActionLiteral bindToModel(Model model) {
48 var adapter = model.getAdapter(ModificationAdapter.class);
49 return tuple -> adapter.deleteObject(tuple, danglingEdges) ? Tuple.of() : null;
50 }
51}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/ModificationActionLiterals.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/ModificationActionLiterals.java
new file mode 100644
index 00000000..31f50ac7
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/actions/ModificationActionLiterals.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.modification.actions;
7
8import tools.refinery.store.dse.modification.DanglingEdges;
9import tools.refinery.store.query.term.NodeVariable;
10
11public class ModificationActionLiterals {
12 private ModificationActionLiterals() {
13 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static CreateActionLiteral create(NodeVariable variable) {
17 return new CreateActionLiteral(variable);
18 }
19
20 public static DeleteActionLiteral delete(NodeVariable variable, DanglingEdges danglingEdges) {
21 return new DeleteActionLiteral(variable, danglingEdges);
22 }
23}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java
index b2a80d71..4e77c462 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/modification/internal/ModificationAdapterImpl.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.dse.modification.internal; 6package tools.refinery.store.dse.modification.internal;
7 7
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.dse.modification.DanglingEdges;
9import tools.refinery.store.dse.modification.ModificationAdapter; 10import tools.refinery.store.dse.modification.ModificationAdapter;
10import tools.refinery.store.model.Interpretation; 11import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.model.Model; 12import tools.refinery.store.model.Model;
@@ -13,6 +14,8 @@ import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.tuple.Tuple; 14import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.Tuple1; 15import tools.refinery.store.tuple.Tuple1;
15 16
17import java.util.HashSet;
18
16public class ModificationAdapterImpl implements ModificationAdapter { 19public class ModificationAdapterImpl implements ModificationAdapter {
17 static final Symbol<Integer> NEXT_ID = Symbol.of("NEXT_ID", 0, Integer.class, 0); 20 static final Symbol<Integer> NEXT_ID = Symbol.of("NEXT_ID", 0, Integer.class, 0);
18 21
@@ -49,14 +52,56 @@ public class ModificationAdapterImpl implements ModificationAdapter {
49 } 52 }
50 53
51 @Override 54 @Override
52 public Tuple deleteObject(Tuple tuple) { 55 public boolean deleteObject(Tuple tuple, DanglingEdges danglingEdges) {
53 if (tuple.getSize() != 1) { 56 if (tuple.getSize() != 1) {
54 throw new IllegalArgumentException("Tuple size must be 1"); 57 throw new IllegalArgumentException("Tuple size must be 1");
55 } 58 }
56// TODO: implement more efficient deletion 59 int objectId = tuple.get(0);
57 if (tuple.get(0) == getModelSize() - 1) { 60 if (danglingEdges == DanglingEdges.DELETE) {
58 nodeCountInterpretation.put(Tuple.of(), getModelSize() - 1); 61 deleteDanglingEdges(objectId);
62 } else if (danglingEdges == DanglingEdges.FAIL && hasDanglingEdges(objectId)) {
63 return false;
64
65 }
66 int modelSize = getModelSize();
67 if (objectId == modelSize - 1) {
68 nodeCountInterpretation.put(Tuple.of(), modelSize - 1);
69 }
70 return true;
71 }
72
73 private void deleteDanglingEdges(int objectId) {
74 for (var symbol : model.getStore().getSymbols()) {
75 deleteDanglingEdges(objectId, (Symbol<?>) symbol);
76 }
77 }
78
79 private <T> void deleteDanglingEdges(int objectId, Symbol<T> symbol) {
80 var interpretation = model.getInterpretation(symbol);
81 var toDelete = new HashSet<Tuple>();
82 int arity = symbol.arity();
83 for (int i = 0; i < arity; i++) {
84 var cursor = interpretation.getAdjacent(i, objectId);
85 while (cursor.move()) {
86 toDelete.add(cursor.getKey());
87 }
88 }
89 var defaultValue = symbol.defaultValue();
90 for (var tuple : toDelete) {
91 interpretation.put(tuple, defaultValue);
92 }
93 }
94
95 private boolean hasDanglingEdges(int objectId) {
96 for (var symbol : model.getStore().getSymbols()) {
97 var interpretation = model.getInterpretation(symbol);
98 int arity = symbol.arity();
99 for (int i = 0; i < arity; i++) {
100 if (interpretation.getAdjacentSize(i, objectId) > 0) {
101 return true;
102 }
103 }
59 } 104 }
60 return tuple; 105 return false;
61 } 106 }
62} 107}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java
index 0b9aae9c..4ccba6f7 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/strategy/BestFirstStoreManager.java
@@ -5,7 +5,6 @@
5 */ 5 */
6package tools.refinery.store.dse.strategy; 6package tools.refinery.store.dse.strategy;
7 7
8import org.eclipse.collections.api.block.procedure.Procedure;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter; 8import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter;
10import tools.refinery.store.dse.transition.VersionWithObjectiveValue; 9import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
11import tools.refinery.store.dse.transition.statespace.ActivationStore; 10import tools.refinery.store.dse.transition.statespace.ActivationStore;
@@ -24,6 +23,8 @@ import tools.refinery.visualization.ModelVisualizerStoreAdapter;
24import tools.refinery.visualization.statespace.VisualizationStore; 23import tools.refinery.visualization.statespace.VisualizationStore;
25import tools.refinery.visualization.statespace.internal.VisualizationStoreImpl; 24import tools.refinery.visualization.statespace.internal.VisualizationStoreImpl;
26 25
26import java.util.function.Consumer;
27
27public class BestFirstStoreManager { 28public class BestFirstStoreManager {
28 29
29 ModelStore modelStore; 30 ModelStore modelStore;
@@ -39,7 +40,7 @@ public class BestFirstStoreManager {
39 modelStore.getAdapter(DesignSpaceExplorationStoreAdapter.class); 40 modelStore.getAdapter(DesignSpaceExplorationStoreAdapter.class);
40 41
41 objectiveStore = new ObjectivePriorityQueueImpl(storeAdapter.getObjectives()); 42 objectiveStore = new ObjectivePriorityQueueImpl(storeAdapter.getObjectives());
42 Procedure<VersionWithObjectiveValue> whenAllActivationsVisited = x -> objectiveStore.remove(x); 43 Consumer<VersionWithObjectiveValue> whenAllActivationsVisited = x -> objectiveStore.remove(x);
43 activationStore = new ActivationStoreImpl(storeAdapter.getTransformations().size(), whenAllActivationsVisited); 44 activationStore = new ActivationStoreImpl(storeAdapter.getTransformations().size(), whenAllActivationsVisited);
44 solutionStore = new SolutionStoreImpl(50); 45 solutionStore = new SolutionStoreImpl(50);
45 equivalenceClassStore = new FastEquivalenceClassStore(modelStore.getAdapter(StateCoderStoreAdapter.class)) { 46 equivalenceClassStore = new FastEquivalenceClassStore(modelStore.getAdapter(StateCoderStoreAdapter.class)) {
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java
index 37448309..d326f1dd 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationAdapter.java
@@ -7,25 +7,22 @@ package tools.refinery.store.dse.transition;
7 7
8import tools.refinery.store.adapter.ModelAdapter; 8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.dse.transition.internal.DesignSpaceExplorationBuilderImpl; 9import tools.refinery.store.dse.transition.internal.DesignSpaceExplorationBuilderImpl;
10import tools.refinery.store.map.Version;
11import tools.refinery.store.tuple.Tuple;
12import tools.refinery.store.tuple.Tuple1;
13 10
14import java.util.Collection;
15import java.util.List; 11import java.util.List;
16 12
17public interface DesignSpaceExplorationAdapter extends ModelAdapter { 13public interface DesignSpaceExplorationAdapter extends ModelAdapter {
18
19
20
21 @Override 14 @Override
22 DesignSpaceExplorationStoreAdapter getStoreAdapter(); 15 DesignSpaceExplorationStoreAdapter getStoreAdapter();
23 16
24 static DesignSpaceExplorationBuilder builder() { 17 static DesignSpaceExplorationBuilder builder() {
25 return new DesignSpaceExplorationBuilderImpl(); 18 return new DesignSpaceExplorationBuilderImpl();
26 } 19 }
20
27 List<Transformation> getTransformations(); 21 List<Transformation> getTransformations();
22
28 boolean checkAccept(); 23 boolean checkAccept();
24
29 boolean checkExclude(); 25 boolean checkExclude();
26
30 ObjectiveValue getObjectiveValue(); 27 ObjectiveValue getObjectiveValue();
31} 28}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java
index 3855a20a..800cf8f7 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationBuilder.java
@@ -12,14 +12,16 @@ import tools.refinery.store.dse.transition.objectives.Objective;
12import java.util.Collection; 12import java.util.Collection;
13import java.util.List; 13import java.util.List;
14 14
15// Builder pattern with methods returning {@code this} for convenience.
16@SuppressWarnings("UnusedReturnValue")
15public interface DesignSpaceExplorationBuilder extends ModelAdapterBuilder { 17public interface DesignSpaceExplorationBuilder extends ModelAdapterBuilder {
18 DesignSpaceExplorationBuilder transformation(Rule transformationRuleDefinition);
16 19
17 DesignSpaceExplorationBuilder transformation(TransformationRule transformationRuleDefinition); 20 default DesignSpaceExplorationBuilder transformations(Rule... transformationRuleDefinitions) {
18 default DesignSpaceExplorationBuilder transformations(TransformationRule... transformationRuleDefinitions) {
19 return transformations(List.of(transformationRuleDefinitions)); 21 return transformations(List.of(transformationRuleDefinitions));
20 } 22 }
21 23
22 default DesignSpaceExplorationBuilder transformations(Collection<? extends TransformationRule> transformationRules) { 24 default DesignSpaceExplorationBuilder transformations(Collection<? extends Rule> transformationRules) {
23 transformationRules.forEach(this::transformation); 25 transformationRules.forEach(this::transformation);
24 return this; 26 return this;
25 } 27 }
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java
index 5c8c7a4d..fb082fae 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/DesignSpaceExplorationStoreAdapter.java
@@ -17,7 +17,7 @@ public interface DesignSpaceExplorationStoreAdapter extends ModelStoreAdapter
17 @Override 17 @Override
18 DesignSpaceExplorationAdapter createModelAdapter(Model model); 18 DesignSpaceExplorationAdapter createModelAdapter(Model model);
19 19
20 List<TransformationRule> getTransformations(); 20 List<Rule> getTransformations();
21 21
22 List<Criterion> getAccepts(); 22 List<Criterion> getAccepts();
23 23
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Rule.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Rule.java
new file mode 100644
index 00000000..ff45ed3e
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Rule.java
@@ -0,0 +1,99 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.dse.transition.actions.Action;
9import tools.refinery.store.dse.transition.actions.BoundAction;
10import tools.refinery.store.dse.transition.callback.*;
11import tools.refinery.store.model.Model;
12import tools.refinery.store.query.dnf.RelationalQuery;
13
14public class Rule {
15 private final String name;
16 private final RelationalQuery precondition;
17 private final Action action;
18
19 public Rule(String name, RelationalQuery precondition, Action action) {
20 if (precondition.arity() != action.getArity()) {
21 throw new IllegalArgumentException("Expected an action clause with %d parameters, got %d instead"
22 .formatted(precondition.arity(), action.getArity()));
23 }
24 this.name = name;
25 this.precondition = precondition;
26 this.action = action;
27 }
28
29 public String getName() {
30 return name;
31 }
32
33 public RelationalQuery getPrecondition() {
34 return precondition;
35 }
36
37 public BoundAction createAction(Model model) {
38 return action.bindToModel(model);
39 }
40
41 public static RuleBuilder builder(String name) {
42 return new RuleBuilder(name);
43 }
44
45 public static RuleBuilder builder() {
46 return builder(null);
47 }
48
49 public static Rule of(String name, RuleCallback0 callback) {
50 var builder = builder(name);
51 callback.accept(builder);
52 return builder.build();
53 }
54
55 public static Rule of(RuleCallback0 callback) {
56 return of(null, callback);
57 }
58
59 public static Rule of(String name, RuleCallback1 callback) {
60 var builder = builder(name);
61 callback.accept(builder, builder.parameter("p1"));
62 return builder.build();
63 }
64
65 public static Rule of(RuleCallback1 callback) {
66 return of(null, callback);
67 }
68
69 public static Rule of(String name, RuleCallback2 callback) {
70 var builder = builder(name);
71 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"));
72 return builder.build();
73 }
74
75 public static Rule of(RuleCallback2 callback) {
76 return of(null, callback);
77 }
78
79 public static Rule of(String name, RuleCallback3 callback) {
80 var builder = builder(name);
81 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"));
82 return builder.build();
83 }
84
85 public static Rule of(RuleCallback3 callback) {
86 return of(null, callback);
87 }
88
89 public static Rule of(String name, RuleCallback4 callback) {
90 var builder = builder(name);
91 callback.accept(builder, builder.parameter("p1"), builder.parameter("p2"), builder.parameter("p3"),
92 builder.parameter("p4"));
93 return builder.build();
94 }
95
96 public static Rule of(RuleCallback4 callback) {
97 return of(null, callback);
98 }
99}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/RuleBuilder.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/RuleBuilder.java
new file mode 100644
index 00000000..865ac369
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/RuleBuilder.java
@@ -0,0 +1,71 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.dse.transition.actions.Action;
9import tools.refinery.store.dse.transition.actions.ActionLiteral;
10import tools.refinery.store.dse.transition.callback.*;
11import tools.refinery.store.query.dnf.AbstractQueryBuilder;
12import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.List;
16
17public class RuleBuilder extends AbstractQueryBuilder<RuleBuilder> {
18 private final String name;
19 private List<ActionLiteral> action;
20
21 RuleBuilder(String name) {
22 super(Dnf.builder(name == null ? null : name + "#precondition"));
23 this.name = name;
24 }
25
26 @Override
27 protected RuleBuilder self() {
28 return this;
29 }
30
31 public RuleBuilder action(ActionLiteral... literals) {
32 return action(List.of(literals));
33 }
34
35 public RuleBuilder action(List<? extends ActionLiteral> literals) {
36 if (this.action != null) {
37 throw new IllegalStateException("Actions have already been set");
38 }
39 this.action = List.copyOf(literals);
40 return this;
41 }
42
43 public RuleBuilder action(Action action) {
44 return action(action.getActionLiterals());
45 }
46
47 public RuleBuilder action(ActionCallback0 callback) {
48 return action(callback.toLiterals());
49 }
50
51 public RuleBuilder action(ActionCallback1 callback) {
52 return action(callback.toLiterals(Variable.of("v1")));
53 }
54
55 public RuleBuilder action(ActionCallback2 callback) {
56 return action(callback.toLiterals(Variable.of("v1"), Variable.of("v2")));
57 }
58
59 public RuleBuilder action(ActionCallback3 callback) {
60 return action(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3")));
61 }
62
63 public RuleBuilder action(ActionCallback4 callback) {
64 return action(callback.toLiterals(Variable.of("v1"), Variable.of("v2"), Variable.of("v3"), Variable.of("v4")));
65 }
66
67 public Rule build() {
68 var precondition = dnfBuilder.build().asRelation();
69 return new Rule(name, precondition, Action.ofPrecondition(precondition, action));
70 }
71}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java
index 2cce738f..0eeccbdf 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/Transformation.java
@@ -5,25 +5,27 @@
5 */ 5 */
6package tools.refinery.store.dse.transition; 6package tools.refinery.store.dse.transition;
7 7
8import org.eclipse.collections.api.block.procedure.Procedure; 8import tools.refinery.store.dse.transition.actions.BoundAction;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.ModelQueryAdapter;
9import tools.refinery.store.query.resultset.OrderedResultSet; 11import tools.refinery.store.query.resultset.OrderedResultSet;
10import tools.refinery.store.query.resultset.ResultSet; 12import tools.refinery.store.query.resultset.ResultSet;
11import tools.refinery.store.tuple.Tuple; 13import tools.refinery.store.tuple.Tuple;
12 14
13public class Transformation { 15public class Transformation {
14 private final TransformationRule definition; 16 private final Rule definition;
15
16 private final OrderedResultSet<Boolean> activations; 17 private final OrderedResultSet<Boolean> activations;
18 private final BoundAction action;
17 19
18 private final Procedure<Tuple> action; 20 public Transformation(Model model, Rule definition) {
19
20 public Transformation(TransformationRule definition, OrderedResultSet<Boolean> activations, Procedure<Tuple> action) {
21 this.definition = definition; 21 this.definition = definition;
22 this.activations = activations; 22 var precondition = definition.getPrecondition();
23 this.action = action; 23 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
24 activations = new OrderedResultSet<>(queryEngine.getResultSet(precondition));
25 action = definition.createAction(model);
24 } 26 }
25 27
26 public TransformationRule getDefinition() { 28 public Rule getDefinition() {
27 return definition; 29 return definition;
28 } 30 }
29 31
@@ -36,8 +38,6 @@ public class Transformation {
36 } 38 }
37 39
38 public boolean fireActivation(Tuple activation) { 40 public boolean fireActivation(Tuple activation) {
39 action.accept(activation); 41 return action.fire(activation);
40 //queryEngine.flushChanges();
41 return true;
42 } 42 }
43} 43}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java
deleted file mode 100644
index d64a3db1..00000000
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/TransformationRule.java
+++ /dev/null
@@ -1,63 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.RelationalQuery;
13import tools.refinery.store.dse.ActionFactory;
14import tools.refinery.store.query.resultset.OrderedResultSet;
15import tools.refinery.store.query.resultset.ResultSet;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.*;
19
20public class TransformationRule {
21
22 private final String name;
23 private final RelationalQuery precondition;
24 private final ActionFactory actionFactory;
25
26 private Random random;
27 private ModelQueryAdapter queryEngine;
28
29 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory) {
30 this(name, precondition, actionFactory, new Random());
31 }
32
33 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory, long seed) {
34 this(name, precondition, actionFactory, new Random(seed));
35 }
36
37 public TransformationRule(String name, RelationalQuery precondition, ActionFactory actionFactory, Random random) {
38 this.name = name;
39 this.precondition = precondition;
40 this.actionFactory = actionFactory;
41 this.random = random;
42 }
43 public void doConfigure(ModelStoreBuilder storeBuilder) {
44 var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class);
45 queryBuilder.query(this.precondition);
46 }
47
48 public Transformation prepare(Model model) {
49 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
50 var activations = new OrderedResultSet<>(queryEngine.getResultSet(precondition));
51 var action = actionFactory.prepare(model);
52 return new Transformation(this,activations,action);
53 }
54
55 public String getName() {
56 return name;
57 }
58
59 public RelationalQuery getPrecondition() {
60 return precondition;
61 }
62
63}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/AbstractActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/AbstractActionLiteral.java
new file mode 100644
index 00000000..e30f06bb
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/AbstractActionLiteral.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8public abstract class AbstractActionLiteral implements ActionLiteral {
9}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/Action.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/Action.java
new file mode 100644
index 00000000..d63ddfdd
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/Action.java
@@ -0,0 +1,132 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
9import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
10import org.jetbrains.annotations.Nullable;
11import tools.refinery.store.model.Model;
12import tools.refinery.store.query.dnf.RelationalQuery;
13import tools.refinery.store.query.dnf.SymbolicParameter;
14import tools.refinery.store.query.term.NodeVariable;
15
16import java.util.*;
17
18public class Action {
19 private final List<NodeVariable> parameters;
20 private final Set<NodeVariable> localVariables;
21 private final List<ActionLiteral> actionLiterals;
22 private final int[] @Nullable [] inputAllocations;
23 private final int[] @Nullable [] outputAllocations;
24
25 public Action(List<NodeVariable> parameters, List<? extends ActionLiteral> actionLiterals) {
26 this.parameters = List.copyOf(parameters);
27 this.actionLiterals = List.copyOf(actionLiterals);
28 var allocation = ObjectIntMaps.mutable.<NodeVariable>empty();
29 int arity = parameters.size();
30 for (int i = 0; i < arity; i++) {
31 allocation.put(parameters.get(i), i);
32 }
33 var mutableLocalVariables = new LinkedHashSet<NodeVariable>();
34 int size = actionLiterals.size();
35 inputAllocations = new int[size][];
36 outputAllocations = new int[size][];
37 for (int i = 0; i < size; i++) {
38 computeInputAllocation(i, parameters, allocation);
39 computeOutputAllocation(i, mutableLocalVariables, allocation);
40 }
41 this.localVariables = Collections.unmodifiableSet(mutableLocalVariables);
42 }
43
44 private void computeInputAllocation(int actionIndex, List<NodeVariable> parameters,
45 MutableObjectIntMap<NodeVariable> allocation) {
46 var actionLiteral = actionLiterals.get(actionIndex);
47 var inputVariables = actionLiteral.getInputVariables();
48 if (inputVariables.equals(parameters)) {
49 // Identity mappings use a {@code null} allocation to pass the activation tuple unchanged.
50 return;
51 }
52 var inputs = new int[inputVariables.size()];
53 for (int i = 0; i < inputs.length; i++) {
54 var variable = inputVariables.get(i);
55 if (!allocation.containsKey(variable)) {
56 throw new IllegalArgumentException("Unbound input variable %s of action literal %s"
57 .formatted(variable, actionLiteral));
58 }
59 inputs[i] = allocation.get(variable);
60 }
61 inputAllocations[actionIndex] = inputs;
62 }
63
64 private void computeOutputAllocation(int actionIndex, Set<NodeVariable> mutableLocalVariable,
65 MutableObjectIntMap<NodeVariable> allocation) {
66 var actionLiteral = actionLiterals.get(actionIndex);
67 var outputVariables = actionLiteral.getOutputVariables();
68 int size = outputVariables.size();
69 if (size == 0) {
70 // Identity mappings use a {@code null} allocation to avoid iterating over the output tuple.
71 return;
72 }
73 if (size >= 2 && new HashSet<>(outputVariables).size() != size) {
74 throw new IllegalArgumentException("Action literal %s has duplicate output variables %s"
75 .formatted(actionLiteral, outputVariables));
76 }
77 int arity = parameters.size();
78 var outputs = new int[size];
79 for (int i = 0; i < size; i++) {
80 var variable = outputVariables.get(i);
81 if (allocation.containsKey(variable)) {
82 throw new IllegalArgumentException("Output variable %s of action literal %s was already assigned"
83 .formatted(variable, actionLiteral));
84 }
85 int variableId = mutableLocalVariable.size();
86 allocation.put(variable, arity + variableId);
87 outputs[i] = variableId;
88 mutableLocalVariable.add(variable);
89 }
90 outputAllocations[actionIndex] = outputs;
91 }
92
93 public List<NodeVariable> getParameters() {
94 return parameters;
95 }
96
97 public int getArity() {
98 return parameters.size();
99 }
100
101 public Set<NodeVariable> getLocalVariables() {
102 return localVariables;
103 }
104
105 public List<ActionLiteral> getActionLiterals() {
106 return actionLiterals;
107 }
108
109 int @Nullable [] getInputAllocation(int actionIndex) {
110 return inputAllocations[actionIndex];
111 }
112
113 int @Nullable [] getOutputAllocation(int actionIndex) {
114 return outputAllocations[actionIndex];
115 }
116
117 public BoundAction bindToModel(Model model) {
118 return new BoundAction(this, model);
119 }
120
121 public static Action ofSymbolicParameters(List<SymbolicParameter> symbolicParameters,
122 List<? extends ActionLiteral> actionLiterals) {
123 var nodeVariables = symbolicParameters.stream()
124 .map(symbolicParameter -> symbolicParameter.getVariable().asNodeVariable())
125 .toList();
126 return new Action(nodeVariables, actionLiterals);
127 }
128
129 public static Action ofPrecondition(RelationalQuery precondition, List<? extends ActionLiteral> actionLiterals) {
130 return ofSymbolicParameters(precondition.getDnf().getSymbolicParameters(), actionLiterals);
131 }
132}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiteral.java
new file mode 100644
index 00000000..a721ef73
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiteral.java
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.List;
12
13public interface ActionLiteral {
14 List<NodeVariable> getInputVariables();
15
16 List<NodeVariable> getOutputVariables();
17
18 BoundActionLiteral bindToModel(Model model);
19}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiterals.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiterals.java
new file mode 100644
index 00000000..275e1e25
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/ActionLiterals.java
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import tools.refinery.store.query.term.NodeVariable;
9import tools.refinery.store.representation.Symbol;
10
11import java.util.List;
12import java.util.Objects;
13
14public final class ActionLiterals {
15 private ActionLiterals() {
16 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
17 }
18
19 public static <T> PutActionLiteral<T> put(Symbol<T> symbol, T value, NodeVariable... parameters) {
20 return new PutActionLiteral<>(symbol, value, List.of(parameters));
21 }
22
23 public static PutActionLiteral<Boolean> add(Symbol<Boolean> symbol, NodeVariable... parameters) {
24 if (!Objects.equals(symbol.defaultValue(), false)) {
25 throw new IllegalArgumentException("Use put to add a value to symbols other than two-valued logic");
26 }
27 return put(symbol, true, parameters);
28 }
29
30 public static <T> PutActionLiteral<T> remove(Symbol<T> symbol, NodeVariable... parameters) {
31 return put(symbol, symbol.defaultValue(), parameters);
32 }
33}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundAction.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundAction.java
new file mode 100644
index 00000000..55f43735
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundAction.java
@@ -0,0 +1,109 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.tuple.Tuple;
11
12public class BoundAction {
13 private final Action action;
14 private final BoundActionLiteral[] boundLiterals;
15 private Tuple activation;
16 private final int[] localVariables;
17
18 BoundAction(Action action, Model model) {
19 this.action = action;
20 var actionLiterals = action.getActionLiterals();
21 int size = actionLiterals.size();
22 boundLiterals = new BoundActionLiteral[size];
23 for (int i = 0; i < size; i++) {
24 boundLiterals[i] = actionLiterals.get(i).bindToModel(model);
25 }
26 localVariables = new int[action.getLocalVariables().size()];
27 }
28
29 public boolean fire(Tuple activation) {
30 if (this.activation != null) {
31 throw new IllegalStateException("Reentrant firing is not allowed");
32 }
33 this.activation = activation;
34 try {
35 int size = boundLiterals.length;
36 for (int i = 0; i < size; i++) {
37 var inputAllocation = action.getInputAllocation(i);
38 var boundLiteral = boundLiterals[i];
39 var input = getInputTuple(inputAllocation);
40 var output = boundLiteral.fire(input);
41 if (output == null) {
42 return false;
43 }
44 var outputAllocation = this.action.getOutputAllocation(i);
45 setOutputTuple(outputAllocation, output);
46 }
47 } finally {
48 this.activation = null;
49 }
50 return true;
51 }
52
53 private Tuple getInputTuple(int @Nullable [] inputAllocation) {
54 if (inputAllocation == null) {
55 // Identity allocation.
56 return activation;
57 }
58 return switch (inputAllocation.length) {
59 case 0 -> Tuple.of();
60 case 1 -> Tuple.of(getInput(inputAllocation[0]));
61 case 2 -> Tuple.of(getInput(inputAllocation[0]), getInput(inputAllocation[1]));
62 case 3 -> Tuple.of(getInput(inputAllocation[0]), getInput(inputAllocation[1]),
63 getInput(inputAllocation[2]));
64 case 4 -> Tuple.of(getInput(inputAllocation[0]), getInput(inputAllocation[1]),
65 getInput(inputAllocation[2]), getInput(inputAllocation[3]));
66 default -> {
67 var elements = new int[inputAllocation.length];
68 for (var i = 0; i < inputAllocation.length; i++) {
69 elements[i] = getInput(inputAllocation[i]);
70 }
71 yield Tuple.of(elements);
72 }
73 };
74 }
75
76 private int getInput(int index) {
77 int arity = action.getArity();
78 return index < arity ? activation.get(index) : localVariables[index - arity];
79 }
80
81 private void setOutputTuple(int @Nullable [] outputAllocation, Tuple output) {
82 if (outputAllocation == null || outputAllocation.length == 0) {
83 return;
84 }
85 switch (outputAllocation.length) {
86 case 1 -> localVariables[outputAllocation[0]] = output.get(0);
87 case 2 -> {
88 localVariables[outputAllocation[0]] = output.get(0);
89 localVariables[outputAllocation[1]] = output.get(1);
90 }
91 case 3 -> {
92 localVariables[outputAllocation[0]] = output.get(0);
93 localVariables[outputAllocation[1]] = output.get(1);
94 localVariables[outputAllocation[2]] = output.get(2);
95 }
96 case 4 -> {
97 localVariables[outputAllocation[0]] = output.get(0);
98 localVariables[outputAllocation[1]] = output.get(1);
99 localVariables[outputAllocation[2]] = output.get(2);
100 localVariables[outputAllocation[3]] = output.get(3);
101 }
102 default -> {
103 for (int i = 0; i < outputAllocation.length; i++) {
104 localVariables[outputAllocation[i]] = output.get(i);
105 }
106 }
107 }
108 }
109}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundActionLiteral.java
new file mode 100644
index 00000000..09c3c58c
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/BoundActionLiteral.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import org.jetbrains.annotations.NotNull;
9import org.jetbrains.annotations.Nullable;
10import tools.refinery.store.tuple.Tuple;
11
12@FunctionalInterface
13public interface BoundActionLiteral {
14 @Nullable
15 Tuple fire(@NotNull Tuple tuple);
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/PutActionLiteral.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/PutActionLiteral.java
new file mode 100644
index 00000000..86288921
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/actions/PutActionLiteral.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.actions;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.List;
14
15public class PutActionLiteral<T> extends AbstractActionLiteral {
16 private final Symbol<T> symbol;
17 private final List<NodeVariable> parameters;
18 private final T value;
19
20 public PutActionLiteral(Symbol<T> symbol, T value, List<NodeVariable> parameters) {
21 if (symbol.arity() != parameters.size()) {
22 throw new IllegalArgumentException("Expected %d parameters for symbol %s, got %d instead"
23 .formatted(symbol.arity(), symbol, parameters.size()));
24 }
25 if (value != null && !symbol.valueType().isInstance(value)) {
26 throw new IllegalArgumentException("Expected value of type %s for symbol %s, got %s of type %s instead"
27 .formatted(symbol.valueType().getName(), symbol, value, value.getClass().getName()));
28 }
29 this.symbol = symbol;
30 this.parameters = List.copyOf(parameters);
31 this.value = value;
32 }
33
34 public Symbol<T> getSymbol() {
35 return symbol;
36 }
37
38 public List<NodeVariable> getParameters() {
39 return parameters;
40 }
41
42 public T getValue() {
43 return value;
44 }
45
46 @Override
47 public List<NodeVariable> getInputVariables() {
48 return getParameters();
49 }
50
51 @Override
52 public List<NodeVariable> getOutputVariables() {
53 return List.of();
54 }
55
56 @Override
57 public BoundActionLiteral bindToModel(Model model) {
58 var interpretation = model.getInterpretation(symbol);
59 return tuple -> {
60 interpretation.put(tuple, value);
61 return Tuple.of();
62 };
63 }
64}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback0.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback0.java
new file mode 100644
index 00000000..1190fdeb
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback0.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.actions.ActionLiteral;
9
10import java.util.List;
11
12@FunctionalInterface
13public interface ActionCallback0 {
14 List<ActionLiteral> toLiterals();
15}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback1.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback1.java
new file mode 100644
index 00000000..869f1a96
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback1.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.actions.ActionLiteral;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.List;
12
13@FunctionalInterface
14public interface ActionCallback1 {
15 List<ActionLiteral> toLiterals(NodeVariable v1);
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback2.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback2.java
new file mode 100644
index 00000000..a648fc93
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback2.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.actions.ActionLiteral;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.List;
12
13@FunctionalInterface
14public interface ActionCallback2 {
15 List<ActionLiteral> toLiterals(NodeVariable v1, NodeVariable v2);
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback3.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback3.java
new file mode 100644
index 00000000..a9b1d334
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback3.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.actions.ActionLiteral;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.List;
12
13@FunctionalInterface
14public interface ActionCallback3 {
15 List<ActionLiteral> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3);
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback4.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback4.java
new file mode 100644
index 00000000..aef1351c
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/ActionCallback4.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.actions.ActionLiteral;
9import tools.refinery.store.query.term.NodeVariable;
10
11import java.util.List;
12
13@FunctionalInterface
14public interface ActionCallback4 {
15 List<ActionLiteral> toLiterals(NodeVariable v1, NodeVariable v2, NodeVariable v3, NodeVariable v4);
16}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback0.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback0.java
new file mode 100644
index 00000000..538c23ba
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback0.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.RuleBuilder;
9
10@FunctionalInterface
11public interface RuleCallback0 {
12 void accept(RuleBuilder builder);
13}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback1.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback1.java
new file mode 100644
index 00000000..bd7bf4f5
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback1.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.RuleBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface RuleCallback1 {
13 void accept(RuleBuilder builder, NodeVariable p1);
14}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback2.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback2.java
new file mode 100644
index 00000000..7b02b68a
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback2.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.RuleBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface RuleCallback2 {
13 void accept(RuleBuilder builder, NodeVariable p1, NodeVariable p2);
14}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback3.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback3.java
new file mode 100644
index 00000000..6f112d48
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback3.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.RuleBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface RuleCallback3 {
13 void accept(RuleBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3);
14}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback4.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback4.java
new file mode 100644
index 00000000..dbcf8567
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/callback/RuleCallback4.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.callback;
7
8import tools.refinery.store.dse.transition.RuleBuilder;
9import tools.refinery.store.query.term.NodeVariable;
10
11@FunctionalInterface
12public interface RuleCallback4 {
13 void accept(RuleBuilder builder, NodeVariable p1, NodeVariable p2, NodeVariable p3, NodeVariable p4);
14}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java
index 4371cc03..a91f6870 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationBuilderImpl.java
@@ -7,13 +7,13 @@ package tools.refinery.store.dse.transition.internal;
7 7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder; 9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.TransformationRule; 10import tools.refinery.store.dse.transition.Rule;
11import tools.refinery.store.dse.transition.objectives.Criterion; 11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.Objective; 12import tools.refinery.store.dse.transition.objectives.Objective;
13import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
14import tools.refinery.store.model.ModelStoreBuilder; 14import tools.refinery.store.model.ModelStoreBuilder;
15import tools.refinery.store.query.ModelQueryBuilder;
15 16
16import java.util.ArrayList;
17import java.util.LinkedHashSet; 17import java.util.LinkedHashSet;
18import java.util.List; 18import java.util.List;
19 19
@@ -21,13 +21,13 @@ public class DesignSpaceExplorationBuilderImpl
21 extends AbstractModelAdapterBuilder<DesignSpaceExplorationStoreAdapterImpl> 21 extends AbstractModelAdapterBuilder<DesignSpaceExplorationStoreAdapterImpl>
22 implements DesignSpaceExplorationBuilder { 22 implements DesignSpaceExplorationBuilder {
23 23
24 LinkedHashSet<TransformationRule> transformationRuleDefinitions = new LinkedHashSet<>(); 24 LinkedHashSet<Rule> transformationRuleDefinitions = new LinkedHashSet<>();
25 LinkedHashSet<Criterion> accepts = new LinkedHashSet<>(); 25 LinkedHashSet<Criterion> accepts = new LinkedHashSet<>();
26 LinkedHashSet<Criterion> excludes = new LinkedHashSet<>(); 26 LinkedHashSet<Criterion> excludes = new LinkedHashSet<>();
27 LinkedHashSet<Objective> objectives = new LinkedHashSet<>(); 27 LinkedHashSet<Objective> objectives = new LinkedHashSet<>();
28 28
29 @Override 29 @Override
30 public DesignSpaceExplorationBuilder transformation(TransformationRule transformationRuleDefinition) { 30 public DesignSpaceExplorationBuilder transformation(Rule transformationRuleDefinition) {
31 transformationRuleDefinitions.add(transformationRuleDefinition); 31 transformationRuleDefinitions.add(transformationRuleDefinition);
32 return this; 32 return this;
33 } 33 }
@@ -53,23 +53,23 @@ public class DesignSpaceExplorationBuilderImpl
53 53
54 @Override 54 @Override
55 protected void doConfigure(ModelStoreBuilder storeBuilder) { 55 protected void doConfigure(ModelStoreBuilder storeBuilder) {
56 transformationRuleDefinitions.forEach(x -> x.doConfigure(storeBuilder)); 56 var queryEngine = storeBuilder.getAdapter(ModelQueryBuilder.class);
57 accepts.forEach(x -> x.doConfigure(storeBuilder)); 57 transformationRuleDefinitions.forEach(x -> queryEngine.query(x.getPrecondition()));
58 excludes.forEach(x -> x.doConfigure(storeBuilder)); 58 accepts.forEach(x -> x.configure(storeBuilder));
59 objectives.forEach(x -> x.doConfigure(storeBuilder)); 59 excludes.forEach(x -> x.configure(storeBuilder));
60 objectives.forEach(x -> x.configure(storeBuilder));
60 61
61 super.doConfigure(storeBuilder); 62 super.doConfigure(storeBuilder);
62 } 63 }
63 64
64 @Override 65 @Override
65 protected DesignSpaceExplorationStoreAdapterImpl doBuild(ModelStore store) { 66 protected DesignSpaceExplorationStoreAdapterImpl doBuild(ModelStore store) {
66 List<TransformationRule> transformationRuleDefinitiions1 = new ArrayList<>(transformationRuleDefinitions); 67 List<Rule> transformationRuleDefinitionsList = List.copyOf(transformationRuleDefinitions);
67 List<Criterion> accepts1 = new ArrayList<>(accepts); 68 List<Criterion> acceptsList = List.copyOf(accepts);
68 List<Criterion> excludes1 = new ArrayList<>(excludes); 69 List<Criterion> excludesList = List.copyOf(excludes);
69 List<Objective> objectives1 = new ArrayList<>(objectives); 70 List<Objective> objectivesList = List.copyOf(objectives);
70 71
71 return new DesignSpaceExplorationStoreAdapterImpl(store, 72 return new DesignSpaceExplorationStoreAdapterImpl(store, transformationRuleDefinitionsList, acceptsList,
72 transformationRuleDefinitiions1, accepts1, 73 excludesList, objectivesList);
73 excludes1, objectives1);
74 } 74 }
75} 75}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java
index 3319e148..bd85e7a6 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/internal/DesignSpaceExplorationStoreAdapterImpl.java
@@ -7,7 +7,7 @@ package tools.refinery.store.dse.transition.internal;
7 7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter; 8import tools.refinery.store.dse.transition.DesignSpaceExplorationStoreAdapter;
9import tools.refinery.store.dse.transition.Transformation; 9import tools.refinery.store.dse.transition.Transformation;
10import tools.refinery.store.dse.transition.TransformationRule; 10import tools.refinery.store.dse.transition.Rule;
11import tools.refinery.store.dse.transition.objectives.Criterion; 11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.CriterionCalculator; 12import tools.refinery.store.dse.transition.objectives.CriterionCalculator;
13import tools.refinery.store.dse.transition.objectives.Objective; 13import tools.refinery.store.dse.transition.objectives.Objective;
@@ -20,18 +20,16 @@ import java.util.List;
20public class DesignSpaceExplorationStoreAdapterImpl implements DesignSpaceExplorationStoreAdapter { 20public class DesignSpaceExplorationStoreAdapterImpl implements DesignSpaceExplorationStoreAdapter {
21 protected final ModelStore store; 21 protected final ModelStore store;
22 22
23 protected final List<TransformationRule> transformationRuleDefinitions; 23 protected final List<Rule> ruleDefinitions;
24 protected final List<Criterion> accepts; 24 protected final List<Criterion> accepts;
25 protected final List<Criterion> excludes; 25 protected final List<Criterion> excludes;
26 protected final List<Objective> objectives; 26 protected final List<Objective> objectives;
27 27
28 public DesignSpaceExplorationStoreAdapterImpl(ModelStore store, 28 public DesignSpaceExplorationStoreAdapterImpl(
29 List<TransformationRule> transformationRuleDefinitions, 29 ModelStore store, List<Rule> ruleDefinitions, List<Criterion> accepts, List<Criterion> excludes,
30 List<Criterion> accepts, List<Criterion> excludes, 30 List<Objective> objectives) {
31 List<Objective> objectives) {
32 this.store = store; 31 this.store = store;
33 32 this.ruleDefinitions = ruleDefinitions;
34 this.transformationRuleDefinitions = transformationRuleDefinitions;
35 this.accepts = accepts; 33 this.accepts = accepts;
36 this.excludes = excludes; 34 this.excludes = excludes;
37 this.objectives = objectives; 35 this.objectives = objectives;
@@ -44,25 +42,31 @@ public class DesignSpaceExplorationStoreAdapterImpl implements DesignSpaceExplor
44 42
45 @Override 43 @Override
46 public DesignSpaceExplorationAdapterImpl createModelAdapter(Model model) { 44 public DesignSpaceExplorationAdapterImpl createModelAdapter(Model model) {
47 final List<Transformation> t = this.transformationRuleDefinitions.stream().map(x->x.prepare(model)).toList(); 45 final List<Transformation> t = this.ruleDefinitions.stream()
48 final List<CriterionCalculator> a = this.accepts.stream().map(x->x.createCalculator(model)).toList(); 46 .map(x -> new Transformation(model, x))
49 final List<CriterionCalculator> e = this.excludes.stream().map(x->x.createCalculator(model)).toList(); 47 .toList();
50 final List<ObjectiveCalculator> o = this.objectives.stream().map(x->x.createCalculator(model)).toList(); 48 final List<CriterionCalculator> a = this.accepts.stream().map(x -> x.createCalculator(model)).toList();
49 final List<CriterionCalculator> e = this.excludes.stream().map(x -> x.createCalculator(model)).toList();
50 final List<ObjectiveCalculator> o = this.objectives.stream().map(x -> x.createCalculator(model)).toList();
51 51
52 return new DesignSpaceExplorationAdapterImpl(model, this, t, a, e, o); 52 return new DesignSpaceExplorationAdapterImpl(model, this, t, a, e, o);
53 } 53 }
54
54 @Override 55 @Override
55 public List<TransformationRule> getTransformations() { 56 public List<Rule> getTransformations() {
56 return transformationRuleDefinitions; 57 return ruleDefinitions;
57 } 58 }
59
58 @Override 60 @Override
59 public List<Criterion> getAccepts() { 61 public List<Criterion> getAccepts() {
60 return accepts; 62 return accepts;
61 } 63 }
64
62 @Override 65 @Override
63 public List<Criterion> getExcludes() { 66 public List<Criterion> getExcludes() {
64 return excludes; 67 return excludes;
65 } 68 }
69
66 @Override 70 @Override
67 public List<Objective> getObjectives() { 71 public List<Objective> getObjectives() {
68 return objectives; 72 return objectives;
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/AndCriterion.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/AndCriterion.java
new file mode 100644
index 00000000..0ad2b7a4
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/AndCriterion.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.literal.Reduction;
11
12import java.util.ArrayList;
13import java.util.Collection;
14
15public final class AndCriterion extends CompositeCriterion {
16 AndCriterion(Collection<? extends Criterion> criteria) {
17 super(criteria);
18 }
19
20 @Override
21 public Reduction getReduction(ModelStore store) {
22 for (var criterion : getCriteria()) {
23 var reduction = criterion.getReduction(store);
24 if (reduction == Reduction.ALWAYS_FALSE) {
25 return Reduction.ALWAYS_FALSE;
26 } else if (reduction == Reduction.NOT_REDUCIBLE) {
27 return Reduction.NOT_REDUCIBLE;
28 }
29 }
30 return Reduction.ALWAYS_TRUE;
31 }
32
33 @Override
34 public CriterionCalculator createCalculator(Model model) {
35 var calculators = new ArrayList<CriterionCalculator>();
36 for (var criterion : getCriteria()) {
37 var reduction = criterion.getReduction(model.getStore());
38 if (reduction == Reduction.ALWAYS_FALSE) {
39 return () -> false;
40 } else if (reduction == Reduction.NOT_REDUCIBLE) {
41 calculators.add(criterion.createCalculator(model));
42 }
43 }
44 return () -> {
45 for (var calculator : calculators) {
46 if (!calculator.isSatisfied()) {
47 return false;
48 }
49 }
50 return true;
51 };
52 }
53}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeCriterion.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeCriterion.java
new file mode 100644
index 00000000..5746cc7e
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeCriterion.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.literal.Reduction;
11
12import java.util.*;
13
14public abstract sealed class CompositeCriterion implements Criterion permits AndCriterion, OrCriterion {
15 private final List<Criterion> criteria;
16
17 protected CompositeCriterion(Collection<? extends Criterion> criteria) {
18 var deDuplicatedCriteria = new LinkedHashSet<Criterion>();
19 for (var criterion : criteria) {
20 if (criterion.getClass() == this.getClass()) {
21 var childCriteria = ((CompositeCriterion) criterion).getCriteria();
22 deDuplicatedCriteria.addAll(childCriteria);
23 } else {
24 deDuplicatedCriteria.add(criterion);
25 }
26 }
27 this.criteria = List.copyOf(deDuplicatedCriteria);
28 }
29
30 public List<Criterion> getCriteria() {
31 return criteria;
32 }
33
34 @Override
35 public abstract Reduction getReduction(ModelStore store);
36
37 @Override
38 public void configure(ModelStoreBuilder storeBuilder) {
39 for (var criterion : criteria) {
40 criterion.configure(storeBuilder);
41 }
42 }
43}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeObjective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeObjective.java
new file mode 100644
index 00000000..192a824b
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CompositeObjective.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.model.ModelStoreBuilder;
11
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.List;
16
17public class CompositeObjective implements Objective {
18 private final List<Objective> objectives;
19
20 CompositeObjective(Collection<? extends Objective> objectives) {
21 var unwrappedObjectives = new ArrayList<Objective>();
22 for (var objective : objectives) {
23 if (objective instanceof CompositeObjective compositeObjective) {
24 unwrappedObjectives.addAll(compositeObjective.getObjectives());
25 } else {
26 unwrappedObjectives.add(objective);
27 }
28 }
29 this.objectives = Collections.unmodifiableList(unwrappedObjectives);
30 }
31
32 public List<Objective> getObjectives() {
33 return objectives;
34 }
35
36 @Override
37 public boolean isAlwaysZero(ModelStore store) {
38 for (var objective : objectives) {
39 if (!objective.isAlwaysZero(store)) {
40 return false;
41 }
42 }
43 return true;
44 }
45
46 @Override
47 public ObjectiveCalculator createCalculator(Model model) {
48 var calculators = new ArrayList<ObjectiveCalculator>();
49 for (var objective : objectives) {
50 if (!objective.isAlwaysZero(model.getStore())) {
51 calculators.add(objective.createCalculator(model));
52 }
53 }
54 return () -> {
55 double value = 0;
56 for (var calculator : calculators) {
57 value += calculator.getValue();
58 }
59 return value;
60 };
61 }
62
63 @Override
64 public void configure(ModelStoreBuilder storeBuilder) {
65 for (var objective : objectives) {
66 objective.configure(storeBuilder);
67 }
68 }
69}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CountObjective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CountObjective.java
new file mode 100644
index 00000000..fbd05ded
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/CountObjective.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.ModelQueryBuilder;
13import tools.refinery.store.query.ModelQueryStoreAdapter;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.query.literal.Reduction;
16
17public class CountObjective implements Objective {
18 private final RelationalQuery query;
19 private final double weight;
20
21 public CountObjective(RelationalQuery query) {
22 this(query, 1);
23 }
24
25 public CountObjective(RelationalQuery query, double weight) {
26 this.query = query;
27 this.weight = weight;
28 }
29
30 @Override
31 public boolean isAlwaysZero(ModelStore store) {
32 var queryStore = store.getAdapter(ModelQueryStoreAdapter.class);
33 var canonicalQuery = queryStore.getCanonicalQuery(query);
34 return canonicalQuery.getDnf().getReduction() == Reduction.ALWAYS_FALSE;
35 }
36
37 @Override
38 public ObjectiveCalculator createCalculator(Model model) {
39 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(query);
40 return () -> resultSet.size() * weight;
41 }
42
43 @Override
44 public void configure(ModelStoreBuilder storeBuilder) {
45 storeBuilder.getAdapter(ModelQueryBuilder.class).query(query);
46 }
47}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criteria.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criteria.java
new file mode 100644
index 00000000..0e4ec5c9
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criteria.java
@@ -0,0 +1,47 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.query.dnf.AnyQuery;
9
10import java.util.Collection;
11import java.util.List;
12
13public final class Criteria {
14 private Criteria() {
15 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
16 }
17
18 public static QueryCriterion whenHasMatch(AnyQuery query) {
19 return new QueryCriterion(query, true);
20 }
21
22 public static QueryCriterion whenNoMatch(AnyQuery query) {
23 return new QueryCriterion(query, false);
24 }
25
26 public static Criterion and(Criterion... criteria) {
27 return and(List.of(criteria));
28 }
29
30 public static Criterion and(Collection<? extends Criterion> criteria) {
31 if (criteria.size() == 1) {
32 return criteria.iterator().next();
33 }
34 return new AndCriterion(criteria);
35 }
36
37 public static Criterion or(Criterion... criteria) {
38 return or(List.of(criteria));
39 }
40
41 public static Criterion or(Collection<? extends Criterion> criteria) {
42 if (criteria.size() == 1) {
43 return criteria.iterator().next();
44 }
45 return new OrCriterion(criteria);
46 }
47}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java
index 66ca6f5e..c827f20e 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Criterion.java
@@ -6,10 +6,17 @@
6package tools.refinery.store.dse.transition.objectives; 6package tools.refinery.store.dse.transition.objectives;
7 7
8import tools.refinery.store.model.Model; 8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.model.ModelStoreBuilder; 10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.query.literal.Reduction;
10 12
11public interface Criterion { 13public interface Criterion {
12 default void doConfigure(ModelStoreBuilder storeBuilder) { 14 default void configure(ModelStoreBuilder storeBuilder) {
13 } 15 }
16
17 default Reduction getReduction(ModelStore store) {
18 return Reduction.NOT_REDUCIBLE;
19 }
20
14 CriterionCalculator createCalculator(Model model); 21 CriterionCalculator createCalculator(Model model);
15} 22}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java
index b5924455..49c34d87 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objective.java
@@ -6,10 +6,18 @@
6package tools.refinery.store.dse.transition.objectives; 6package tools.refinery.store.dse.transition.objectives;
7 7
8import tools.refinery.store.model.Model; 8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.model.ModelStoreBuilder; 10import tools.refinery.store.model.ModelStoreBuilder;
10 11
11public interface Objective { 12public interface Objective {
12 default void doConfigure(ModelStoreBuilder storeBuilder) { 13 default void configure(ModelStoreBuilder storeBuilder) {
13 } 14 }
15
16 // The name {@code isAlwaysZero} is more straightforward than something like {@code canBeNonZero}.
17 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
18 default boolean isAlwaysZero(ModelStore store) {
19 return false;
20 }
21
14 ObjectiveCalculator createCalculator(Model model); 22 ObjectiveCalculator createCalculator(Model model);
15} 23}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objectives.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objectives.java
new file mode 100644
index 00000000..e552d14c
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/Objectives.java
@@ -0,0 +1,41 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.query.dnf.FunctionalQuery;
9import tools.refinery.store.query.dnf.RelationalQuery;
10
11import java.util.Collection;
12import java.util.List;
13
14public final class Objectives {
15 private Objectives() {
16 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
17 }
18
19 public static CountObjective count(RelationalQuery query, double weight) {
20 return new CountObjective(query, weight);
21 }
22
23 public static CountObjective count(RelationalQuery query) {
24 return new CountObjective(query);
25 }
26
27 public static QueryObjective value(FunctionalQuery<? extends Number> query) {
28 return new QueryObjective(query);
29 }
30
31 public static Objective sum(Objective... objectives) {
32 return sum(List.of(objectives));
33 }
34
35 public static Objective sum(Collection<? extends Objective> objectives) {
36 if (objectives.size() == 1) {
37 return objectives.iterator().next();
38 }
39 return new CompositeObjective(objectives);
40 }
41}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/OrCriterion.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/OrCriterion.java
new file mode 100644
index 00000000..7a8d7778
--- /dev/null
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/OrCriterion.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse.transition.objectives;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.literal.Reduction;
11
12import java.util.ArrayList;
13import java.util.Collection;
14
15public final class OrCriterion extends CompositeCriterion {
16 OrCriterion(Collection<? extends Criterion> criteria) {
17 super(criteria);
18 }
19
20 @Override
21 public Reduction getReduction(ModelStore store) {
22 for (var criterion : getCriteria()) {
23 var reduction = criterion.getReduction(store);
24 if (reduction == Reduction.ALWAYS_TRUE) {
25 return Reduction.ALWAYS_TRUE;
26 } else if (reduction == Reduction.NOT_REDUCIBLE) {
27 return Reduction.NOT_REDUCIBLE;
28 }
29 }
30 return Reduction.ALWAYS_FALSE;
31 }
32
33 @Override
34 public CriterionCalculator createCalculator(Model model) {
35 var calculators = new ArrayList<CriterionCalculator>();
36 for (var criterion : getCriteria()) {
37 var reduction = criterion.getReduction(model.getStore());
38 if (reduction == Reduction.ALWAYS_TRUE) {
39 return () -> true;
40 } else if (reduction == Reduction.NOT_REDUCIBLE) {
41 calculators.add(criterion.createCalculator(model));
42 }
43 }
44 return () -> {
45 for (var calculator : calculators) {
46 if (calculator.isSatisfied()) {
47 return true;
48 }
49 }
50 return false;
51 };
52 }
53}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriterion.java
index e6dddd53..e15e4e41 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriteria.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryCriterion.java
@@ -6,39 +6,54 @@
6package tools.refinery.store.dse.transition.objectives; 6package tools.refinery.store.dse.transition.objectives;
7 7
8import tools.refinery.store.model.Model; 8import tools.refinery.store.model.Model;
9import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.model.ModelStoreBuilder; 10import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter; 11import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder; 12import tools.refinery.store.query.ModelQueryBuilder;
13import tools.refinery.store.query.ModelQueryStoreAdapter;
12import tools.refinery.store.query.dnf.AnyQuery; 14import tools.refinery.store.query.dnf.AnyQuery;
15import tools.refinery.store.query.literal.Reduction;
13 16
14public class QueryCriteria implements Criterion { 17public class QueryCriterion implements Criterion {
15 protected final boolean satisfiedIfHasMatch; 18 protected final boolean satisfiedIfHasMatch;
16 protected final AnyQuery query; 19 protected final AnyQuery query;
17 20
18 /** 21 /**
19 * Criteria based on the existence of matches evaluated on the model. 22 * Criteria based on the existence of matches evaluated on the model.
20 * @param query The query evaluated on the model. 23 *
24 * @param query The query evaluated on the model.
21 * @param satisfiedIfHasMatch If true, the criteria satisfied if the query has any match on the model. Otherwise, 25 * @param satisfiedIfHasMatch If true, the criteria satisfied if the query has any match on the model. Otherwise,
22 * the criteria satisfied if the query has no match on the model. 26 * the criteria satisfied if the query has no match on the model.
23 */ 27 */
24 public QueryCriteria(AnyQuery query, boolean satisfiedIfHasMatch) { 28 public QueryCriterion(AnyQuery query, boolean satisfiedIfHasMatch) {
25 this.query = query; 29 this.query = query;
26 this.satisfiedIfHasMatch = satisfiedIfHasMatch; 30 this.satisfiedIfHasMatch = satisfiedIfHasMatch;
27 } 31 }
28 32
29 @Override 33 @Override
34 public Reduction getReduction(ModelStore store) {
35 var queryStore = store.getAdapter(ModelQueryStoreAdapter.class);
36 var canonicalQuery = queryStore.getCanonicalQuery(query);
37 var reduction = canonicalQuery.getDnf().getReduction();
38 if (satisfiedIfHasMatch) {
39 return reduction;
40 }
41 return reduction.negate();
42 }
43
44 @Override
30 public CriterionCalculator createCalculator(Model model) { 45 public CriterionCalculator createCalculator(Model model) {
31 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(query); 46 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(query);
32 if(satisfiedIfHasMatch) { 47 if (satisfiedIfHasMatch) {
33 return () -> resultSet.size() > 0; 48 return () -> resultSet.size() > 0;
34 } else { 49 } else {
35 return () -> resultSet.size() == 0; 50 return () -> resultSet.size() == 0;
36 } 51 }
37 } 52 }
38 53
39 @Override 54 @Override
40 public void doConfigure(ModelStoreBuilder storeBuilder) { 55 public void configure(ModelStoreBuilder storeBuilder) {
41 Criterion.super.doConfigure(storeBuilder); 56 Criterion.super.configure(storeBuilder);
42 storeBuilder.getAdapter(ModelQueryBuilder.class).query(query); 57 storeBuilder.getAdapter(ModelQueryBuilder.class).query(query);
43 } 58 }
44} 59}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java
index dfddccfc..9f4bb536 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/objectives/QueryObjective.java
@@ -15,6 +15,10 @@ public class QueryObjective implements Objective {
15 protected final FunctionalQuery<? extends Number> objectiveFunction; 15 protected final FunctionalQuery<? extends Number> objectiveFunction;
16 16
17 public QueryObjective(FunctionalQuery<? extends Number> objectiveFunction) { 17 public QueryObjective(FunctionalQuery<? extends Number> objectiveFunction) {
18 if (objectiveFunction.arity() != 0) {
19 throw new IllegalArgumentException("Objective functions must have 0 parameters, got %d instead"
20 .formatted(objectiveFunction.arity()));
21 }
18 this.objectiveFunction = objectiveFunction; 22 this.objectiveFunction = objectiveFunction;
19 } 23 }
20 24
@@ -23,22 +27,15 @@ public class QueryObjective implements Objective {
23 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(objectiveFunction); 27 var resultSet = model.getAdapter(ModelQueryAdapter.class).getResultSet(objectiveFunction);
24 return () -> { 28 return () -> {
25 var cursor = resultSet.getAll(); 29 var cursor = resultSet.getAll();
26 boolean hasElement = cursor.move(); 30 if (!cursor.move()) {
27 if(hasElement) {
28 double result = cursor.getValue().doubleValue();
29 if(cursor.move()) {
30 throw new IllegalStateException("Query providing the objective function has multiple values!");
31 }
32 return result;
33 } else {
34 throw new IllegalStateException("Query providing the objective function has no values!"); 31 throw new IllegalStateException("Query providing the objective function has no values!");
35 } 32 }
33 return cursor.getValue().doubleValue();
36 }; 34 };
37 } 35 }
38 36
39 @Override 37 @Override
40 public void doConfigure(ModelStoreBuilder storeBuilder) { 38 public void configure(ModelStoreBuilder storeBuilder) {
41 Objective.super.doConfigure(storeBuilder);
42 storeBuilder.getAdapter(ModelQueryBuilder.class).query(objectiveFunction); 39 storeBuilder.getAdapter(ModelQueryBuilder.class).query(objectiveFunction);
43 } 40 }
44} 41}
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java
index 9186741f..4d775b5a 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreImpl.java
@@ -5,19 +5,19 @@
5 */ 5 */
6package tools.refinery.store.dse.transition.statespace.internal; 6package tools.refinery.store.dse.transition.statespace.internal;
7 7
8import org.eclipse.collections.api.block.procedure.Procedure;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue; 8import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.statespace.ActivationStore; 9import tools.refinery.store.dse.transition.statespace.ActivationStore;
11 10
12import java.util.*; 11import java.util.*;
12import java.util.function.Consumer;
13 13
14public class ActivationStoreImpl implements ActivationStore { 14public class ActivationStoreImpl implements ActivationStore {
15 final int numberOfTransformations; 15 final int numberOfTransformations;
16 final Procedure<VersionWithObjectiveValue> actionWhenAllActivationVisited; 16 final Consumer<VersionWithObjectiveValue> actionWhenAllActivationVisited;
17 final Map<VersionWithObjectiveValue, List<ActivationStoreEntry>> versionToActivations; 17 final Map<VersionWithObjectiveValue, List<ActivationStoreEntry>> versionToActivations;
18 18
19 public ActivationStoreImpl(final int numberOfTransformations, 19 public ActivationStoreImpl(final int numberOfTransformations,
20 Procedure<VersionWithObjectiveValue> actionWhenAllActivationVisited) { 20 Consumer<VersionWithObjectiveValue> actionWhenAllActivationVisited) {
21 this.numberOfTransformations = numberOfTransformations; 21 this.numberOfTransformations = numberOfTransformations;
22 this.actionWhenAllActivationVisited = actionWhenAllActivationVisited; 22 this.actionWhenAllActivationVisited = actionWhenAllActivationVisited;
23 versionToActivations = new HashMap<>(); 23 versionToActivations = new HashMap<>();
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java
index 3d4cbfdc..14298bee 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/ActivationStoreListEntry.java
@@ -5,14 +5,14 @@
5 */ 5 */
6package tools.refinery.store.dse.transition.statespace.internal; 6package tools.refinery.store.dse.transition.statespace.internal;
7 7
8import org.eclipse.collections.impl.list.mutable.primitive.IntArrayList; 8import org.eclipse.collections.api.factory.primitive.IntLists;
9import org.eclipse.collections.api.list.primitive.MutableIntList;
9 10
10public class ActivationStoreListEntry extends ActivationStoreEntry { 11public class ActivationStoreListEntry extends ActivationStoreEntry {
11 IntArrayList visitedActivations; 12 private final MutableIntList visitedActivations = IntLists.mutable.empty();
12 13
13 ActivationStoreListEntry(int numberOfActivations) { 14 ActivationStoreListEntry(int numberOfActivations) {
14 super(numberOfActivations); 15 super(numberOfActivations);
15 visitedActivations = new IntArrayList();
16 } 16 }
17 17
18 @Override 18 @Override
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java
index 925e09a3..20a026b6 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/CompleteEquivalenceClassStore.java
@@ -5,7 +5,8 @@
5 */ 5 */
6package tools.refinery.store.dse.transition.statespace.internal; 6package tools.refinery.store.dse.transition.statespace.internal;
7 7
8import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap; 8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue; 10import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore; 11import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
11import tools.refinery.store.statecoding.StateCoderResult; 12import tools.refinery.store.statecoding.StateCoderResult;
@@ -27,11 +28,10 @@ public abstract class CompleteEquivalenceClassStore extends AbstractEquivalenceC
27 } 28 }
28 } 29 }
29 30
30 final IntObjectHashMap<Object> modelCode2Versions; 31 private final MutableIntObjectMap<Object> modelCode2Versions = IntObjectMaps.mutable.empty();
31 32
32 protected CompleteEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) { 33 protected CompleteEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) {
33 super(stateCoderStoreAdapter); 34 super(stateCoderStoreAdapter);
34 this.modelCode2Versions = new IntObjectHashMap<>();
35 } 35 }
36 36
37 @Override 37 @Override
diff --git a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java
index 6eba87d4..6d028124 100644
--- a/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java
+++ b/subprojects/store-dse/src/main/java/tools/refinery/store/dse/transition/statespace/internal/FastEquivalenceClassStore.java
@@ -5,7 +5,8 @@
5 */ 5 */
6package tools.refinery.store.dse.transition.statespace.internal; 6package tools.refinery.store.dse.transition.statespace.internal;
7 7
8import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; 8import org.eclipse.collections.api.factory.primitive.IntSets;
9import org.eclipse.collections.api.set.primitive.MutableIntSet;
9import tools.refinery.store.dse.transition.VersionWithObjectiveValue; 10import tools.refinery.store.dse.transition.VersionWithObjectiveValue;
10import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore; 11import tools.refinery.store.dse.transition.statespace.EquivalenceClassStore;
11import tools.refinery.store.statecoding.StateCoderResult; 12import tools.refinery.store.statecoding.StateCoderResult;
@@ -13,11 +14,10 @@ import tools.refinery.store.statecoding.StateCoderStoreAdapter;
13 14
14public abstract class FastEquivalenceClassStore extends AbstractEquivalenceClassStore implements EquivalenceClassStore { 15public abstract class FastEquivalenceClassStore extends AbstractEquivalenceClassStore implements EquivalenceClassStore {
15 16
16 final IntHashSet codes; 17 private final MutableIntSet codes = IntSets.mutable.empty();
17 18
18 protected FastEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) { 19 protected FastEquivalenceClassStore(StateCoderStoreAdapter stateCoderStoreAdapter) {
19 super(stateCoderStoreAdapter); 20 super(stateCoderStoreAdapter);
20 this.codes = new IntHashSet();
21 } 21 }
22 22
23 @Override 23 @Override
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java
index 831b9ff0..685b88bd 100644
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/CRAExamplesTest.java
@@ -7,19 +7,18 @@ package tools.refinery.store.dse;
7 7
8import org.junit.jupiter.api.Disabled; 8import org.junit.jupiter.api.Disabled;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.dse.modification.DanglingEdges;
10import tools.refinery.store.dse.modification.ModificationAdapter; 11import tools.refinery.store.dse.modification.ModificationAdapter;
11import tools.refinery.store.dse.strategy.BestFirstStoreManager; 12import tools.refinery.store.dse.strategy.BestFirstStoreManager;
13import tools.refinery.store.dse.tests.DummyCriterion;
12import tools.refinery.store.dse.tests.DummyRandomCriterion; 14import tools.refinery.store.dse.tests.DummyRandomCriterion;
13import tools.refinery.store.dse.tests.DummyRandomObjective; 15import tools.refinery.store.dse.tests.DummyRandomObjective;
14import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; 16import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
15import tools.refinery.store.dse.tests.DummyCriterion; 17import tools.refinery.store.dse.transition.Rule;
16import tools.refinery.store.dse.tests.DummyObjective;
17import tools.refinery.store.model.ModelStore; 18import tools.refinery.store.model.ModelStore;
18import tools.refinery.store.query.ModelQueryAdapter; 19import tools.refinery.store.query.ModelQueryAdapter;
19import tools.refinery.store.query.dnf.FunctionalQuery;
20import tools.refinery.store.query.dnf.Query; 20import tools.refinery.store.query.dnf.Query;
21import tools.refinery.store.query.dnf.RelationalQuery; 21import tools.refinery.store.query.dnf.RelationalQuery;
22import tools.refinery.store.dse.transition.TransformationRule;
23import tools.refinery.store.query.term.Variable; 22import tools.refinery.store.query.term.Variable;
24import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 23import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
25import tools.refinery.store.query.view.AnySymbolView; 24import tools.refinery.store.query.view.AnySymbolView;
@@ -32,195 +31,102 @@ import tools.refinery.visualization.internal.FileFormat;
32 31
33import java.util.List; 32import java.util.List;
34 33
34import static tools.refinery.store.dse.modification.actions.ModificationActionLiterals.create;
35import static tools.refinery.store.dse.modification.actions.ModificationActionLiterals.delete;
36import static tools.refinery.store.dse.transition.actions.ActionLiterals.add;
37import static tools.refinery.store.dse.transition.actions.ActionLiterals.remove;
35import static tools.refinery.store.query.literal.Literals.not; 38import static tools.refinery.store.query.literal.Literals.not;
36 39
37class CRAExamplesTest { 40class CRAExamplesTest {
38
39 private static final Symbol<String> name = Symbol.of("Name", 1, String.class); 41 private static final Symbol<String> name = Symbol.of("Name", 1, String.class);
40
41// private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
42 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1); 42 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
43// private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
44 private static final Symbol<Boolean> attribute = Symbol.of("Attribute", 1); 43 private static final Symbol<Boolean> attribute = Symbol.of("Attribute", 1);
45 private static final Symbol<Boolean> method = Symbol.of("Method", 1); 44 private static final Symbol<Boolean> method = Symbol.of("Method", 1);
46
47// private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
48 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2); 45 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
49 private static final Symbol<Boolean> dataDependency = Symbol.of("DataDependency", 2); 46 private static final Symbol<Boolean> dataDependency = Symbol.of("DataDependency", 2);
50 private static final Symbol<Boolean> functionalDependency = Symbol.of("FunctionalDependency", 2); 47 private static final Symbol<Boolean> functionalDependency = Symbol.of("FunctionalDependency", 2);
51 48
52 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
53 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
54
55// private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
56 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement); 49 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
57// private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
58 private static final AnySymbolView attributeView = new KeyOnlyView<>(attribute); 50 private static final AnySymbolView attributeView = new KeyOnlyView<>(attribute);
59 private static final AnySymbolView methodView = new KeyOnlyView<>(method); 51 private static final AnySymbolView methodView = new KeyOnlyView<>(method);
60// private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
61 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates); 52 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
62 private static final AnySymbolView dataDependencyView = new KeyOnlyView<>(dataDependency);
63 private static final AnySymbolView functionalDependencyView = new KeyOnlyView<>(functionalDependency);
64 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
65 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
66 53
67 /*Example Transformation rules*/ 54 private static final RelationalQuery feature = Query.of("Feature", (builder, f) -> builder
68 private static final RelationalQuery feature = Query.of("Feature",
69 (builder, f) -> builder.clause(
70 attributeView.call(f))
71 .clause( 55 .clause(
72 methodView.call(f)) 56 attributeView.call(f)
73 ); 57 )
74 58 .clause(
75 private static final RelationalQuery assignFeaturePreconditionHelper = Query.of("AssignFeaturePreconditionHelper", 59 methodView.call(f)
76 (builder, c, f) -> builder.clause(
77 classElementView.call(c),
78// classesView.call(model, c),
79 encapsulatesView.call(c, f)
80 )); 60 ));
81 61
82 private static final RelationalQuery assignFeaturePrecondition = Query.of("AssignFeaturePrecondition", 62 private static final Rule assignFeatureRule = Rule.of("AssignFeature", (builder, f, c1) -> builder
83 (builder, f, c1) -> builder.clause((c2) -> List.of( 63 .clause(
84// classModelView.call(model),
85 feature.call(f), 64 feature.call(f),
86 classElementView.call(c1), 65 classElementView.call(c1),
87// featuresView.call(model, f), 66 not(encapsulatesView.call(Variable.of(), f))
88 not(assignFeaturePreconditionHelper.call(c2, f)), 67 )
89 not(encapsulatesView.call(c1, f)) 68 .action(
90 ))); 69 add(encapsulates, f, c1)
70 ));
91 71
92 private static final RelationalQuery deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition", 72 private static final Rule deleteEmptyClassRule = Rule.of("DeleteEmptyClass", (builder, c) -> builder
93 (builder, c) -> builder.clause((f) -> List.of( 73 .clause((f) -> List.of(
94// classModelView.call(model),
95 classElementView.call(c), 74 classElementView.call(c),
96// featuresView.call(model, f),
97 not(encapsulatesView.call(c, f)) 75 not(encapsulatesView.call(c, f))
98 ))); 76 ))
99 77 .action(
100 private static final RelationalQuery createClassPreconditionHelper = Query.of("CreateClassPreconditionHelper", 78 remove(classElement, c),
101 (builder, f, c) -> builder.clause( 79 delete(c, DanglingEdges.IGNORE)
102 classElementView.call(c),
103// classesView.call(model, c),
104 encapsulatesView.call(c, f)
105 )); 80 ));
106 81
107 private static final RelationalQuery createClassPrecondition = Query.of("CreateClassPrecondition", 82 private static final Rule createClassRule = Rule.of("CreateClass", (builder, f) -> builder
108 (builder, f) -> builder.clause((c) -> List.of( 83 .clause((c) -> List.of(
109// classModelView.call(model),
110 feature.call(f), 84 feature.call(f),
111 not(createClassPreconditionHelper.call(f, c)) 85 not(encapsulatesView.call(f, c))
86 ))
87 .action((newClass) -> List.of(
88 create(newClass),
89 add(classElement, newClass),
90 add(encapsulates, newClass, f)
112 ))); 91 )));
113 92
114 private static final RelationalQuery moveFeaturePrecondition = Query.of("MoveFeature", 93 private static final Rule moveFeatureRule = Rule.of("MoveFeature", (builder, c1, c2, f) -> builder
115 (builder, c1, c2, f) -> builder.clause( 94 .clause(
116// classModelView.call(model),
117 classElementView.call(c1), 95 classElementView.call(c1),
118 classElementView.call(c2), 96 classElementView.call(c2),
119 c1.notEquivalent(c2), 97 c1.notEquivalent(c2),
120 feature.call(f), 98 feature.call(f),
121// classesView.call(model, c1),
122// classesView.call(model, c2),
123// featuresView.call(model, f),
124 encapsulatesView.call(c1, f) 99 encapsulatesView.call(c1, f)
100 )
101 .action(
102 remove(encapsulates, c1, f),
103 add(encapsulates, c2, f)
125 )); 104 ));
126 105
127 private static final TransformationRule assignFeatureRule = new TransformationRule("AssignFeature",
128 assignFeaturePrecondition,
129 (model) -> {
130// var isEncapsulatedByInterpretation = model.getInterpretation(isEncapsulatedBy);
131 var encapsulatesInterpretation = model.getInterpretation(encapsulates);
132 return ((Tuple activation) -> {
133 var feature = activation.get(0);
134 var classElement = activation.get(1);
135
136// isEncapsulatedByInterpretation.put(Tuple.of(feature, classElement), true);
137 encapsulatesInterpretation.put(Tuple.of(classElement, feature), true);
138 });
139 });
140
141 private static final TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
142 deleteEmptyClassPrecondition,
143 (model) -> {
144// var classesInterpretation = model.getInterpretation(classes);
145 var classElementInterpretation = model.getInterpretation(classElement);
146 return ((Tuple activation) -> {
147 // TODO: can we move modificationAdapter outside?
148 var modificationAdapter = model.getAdapter(ModificationAdapter.class);
149// var modelElement = activation.get(0);
150 var classElement = activation.get(0);
151
152// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
153 classElementInterpretation.put(Tuple.of(classElement), false);
154 modificationAdapter.deleteObject(Tuple.of(classElement));
155 });
156 });
157
158 private static final TransformationRule createClassRule = new TransformationRule("CreateClass",
159 createClassPrecondition,
160 (model) -> {
161 var classElementInterpretation = model.getInterpretation(classElement);
162// var classesInterpretation = model.getInterpretation(classes);
163 var encapsulatesInterpretation = model.getInterpretation(encapsulates);
164 return ((Tuple activation) -> {
165 // TODO: can we move modificationAdapter outside?
166 var modificationAdapter = model.getAdapter(ModificationAdapter.class);
167// var modelElement = activation.get(0);
168 var feature = activation.get(0);
169
170 var newClassElement = modificationAdapter.createObject();
171 var newClassElementId = newClassElement.get(0);
172 classElementInterpretation.put(newClassElement, true);
173// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
174 encapsulatesInterpretation.put(Tuple.of(newClassElementId, feature), true);
175 });
176 });
177
178 private static final TransformationRule moveFeatureRule = new TransformationRule("MoveFeature",
179 moveFeaturePrecondition,
180 (model) -> {
181 var encapsulatesInterpretation = model.getInterpretation(encapsulates);
182 return ((Tuple activation) -> {
183 var classElement1 = activation.get(0);
184 var classElement2 = activation.get(1);
185 var feature = activation.get(2);
186
187 encapsulatesInterpretation.put(Tuple.of(classElement1, feature), false);
188 encapsulatesInterpretation.put(Tuple.of(classElement2, feature), true);
189 });
190 });
191
192 @Test 106 @Test
193 @Disabled("This test is only for debugging purposes") 107 @Disabled("This test is only for debugging purposes")
194 void craTest() { 108 void craTest() {
195 var store = ModelStore.builder() 109 var store = ModelStore.builder()
196 .symbols(classElement, encapsulates, classes, features, attribute, method, dataDependency, 110 .symbols(classElement, encapsulates, attribute, method, dataDependency, functionalDependency, name)
197 functionalDependency, name) 111 .with(ViatraModelQueryAdapter.builder())
198 .with(ViatraModelQueryAdapter.builder()
199 .queries(feature, assignFeaturePreconditionHelper, assignFeaturePrecondition,
200 deleteEmptyClassPrecondition, createClassPreconditionHelper, createClassPrecondition,
201 moveFeaturePrecondition))
202 .with(ModelVisualizerAdapter.builder() 112 .with(ModelVisualizerAdapter.builder()
203 .withOutputpath("test_output") 113 .withOutputpath("test_output")
204 .withFormat(FileFormat.DOT) 114 .withFormat(FileFormat.DOT)
205 .withFormat(FileFormat.SVG) 115 .withFormat(FileFormat.SVG)
206 .saveStates() 116 .saveStates()
207 .saveDesignSpace() 117 .saveDesignSpace())
208 )
209 .with(StateCoderAdapter.builder()) 118 .with(StateCoderAdapter.builder())
210 .with(ModificationAdapter.builder()) 119 .with(ModificationAdapter.builder())
211 .with(DesignSpaceExplorationAdapter.builder() 120 .with(DesignSpaceExplorationAdapter.builder()
212 .transformations(assignFeatureRule, deleteEmptyClassRule, createClassRule, moveFeatureRule) 121 .transformations(assignFeatureRule, deleteEmptyClassRule, createClassRule, moveFeatureRule)
213 .objectives(new DummyRandomObjective()) 122 .objectives(new DummyRandomObjective())
214 .accept(new DummyRandomCriterion()) 123 .accept(new DummyRandomCriterion())
215 .exclude(new DummyCriterion(false)) 124 .exclude(new DummyCriterion(false)))
216 )
217 .build(); 125 .build();
218 126
219 var model = store.createEmptyModel(); 127 var model = store.createEmptyModel();
220// modificationAdapter.setRandom(1);
221 var queryEngine = model.getAdapter(ModelQueryAdapter.class); 128 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
222 129
223// var modelInterpretation = model.getInterpretation(classModel);
224 var nameInterpretation = model.getInterpretation(name); 130 var nameInterpretation = model.getInterpretation(name);
225 var methodInterpretation = model.getInterpretation(method); 131 var methodInterpretation = model.getInterpretation(method);
226 var attributeInterpretation = model.getInterpretation(attribute); 132 var attributeInterpretation = model.getInterpretation(attribute);
@@ -229,7 +135,6 @@ class CRAExamplesTest {
229 135
230 var modificationAdapter = model.getAdapter(ModificationAdapter.class); 136 var modificationAdapter = model.getAdapter(ModificationAdapter.class);
231 137
232// var modelElement = modificationAdapter.createObject();
233 var method1 = modificationAdapter.createObject(); 138 var method1 = modificationAdapter.createObject();
234 var method1Id = method1.get(0); 139 var method1Id = method1.get(0);
235 var method2 = modificationAdapter.createObject(); 140 var method2 = modificationAdapter.createObject();
@@ -259,9 +164,6 @@ class CRAExamplesTest {
259 nameInterpretation.put(attribute4, "A4"); 164 nameInterpretation.put(attribute4, "A4");
260 nameInterpretation.put(attribute5, "A5"); 165 nameInterpretation.put(attribute5, "A5");
261 166
262
263
264// modelInterpretation.put(modelElement, true);
265 methodInterpretation.put(method1, true); 167 methodInterpretation.put(method1, true);
266 methodInterpretation.put(method2, true); 168 methodInterpretation.put(method2, true);
267 methodInterpretation.put(method3, true); 169 methodInterpretation.put(method3, true);
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java
index 87c3892a..baa7c8a4 100644
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DebugTest.java
@@ -9,110 +9,73 @@ import org.junit.jupiter.api.Disabled;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.dse.modification.ModificationAdapter; 10import tools.refinery.store.dse.modification.ModificationAdapter;
11import tools.refinery.store.dse.strategy.BestFirstStoreManager; 11import tools.refinery.store.dse.strategy.BestFirstStoreManager;
12import tools.refinery.store.dse.tests.DummyCriterion;
13import tools.refinery.store.dse.tests.DummyObjective;
14import tools.refinery.store.dse.tests.DummyRandomCriterion; 12import tools.refinery.store.dse.tests.DummyRandomCriterion;
15import tools.refinery.store.dse.tests.DummyRandomObjective; 13import tools.refinery.store.dse.tests.DummyRandomObjective;
16import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter; 14import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
17import tools.refinery.store.dse.transition.TransformationRule; 15import tools.refinery.store.dse.transition.Rule;
18import tools.refinery.store.model.ModelStore; 16import tools.refinery.store.model.ModelStore;
19import tools.refinery.store.query.ModelQueryAdapter; 17import tools.refinery.store.query.ModelQueryAdapter;
20import tools.refinery.store.query.dnf.Query;
21import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 18import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
22import tools.refinery.store.query.view.AnySymbolView; 19import tools.refinery.store.query.view.AnySymbolView;
23import tools.refinery.store.query.view.KeyOnlyView; 20import tools.refinery.store.query.view.KeyOnlyView;
24import tools.refinery.store.representation.Symbol; 21import tools.refinery.store.representation.Symbol;
25import tools.refinery.store.statecoding.StateCoderAdapter; 22import tools.refinery.store.statecoding.StateCoderAdapter;
26import tools.refinery.store.tuple.Tuple;
27import tools.refinery.visualization.ModelVisualizerAdapter; 23import tools.refinery.visualization.ModelVisualizerAdapter;
28import tools.refinery.visualization.internal.FileFormat; 24import tools.refinery.visualization.internal.FileFormat;
29 25
26import java.util.List;
27
28import static tools.refinery.store.dse.modification.actions.ModificationActionLiterals.create;
29import static tools.refinery.store.dse.transition.actions.ActionLiterals.add;
30
30class DebugTest { 31class DebugTest {
31 private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1); 32 private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
32 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1); 33 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
33 private static final Symbol<Boolean> feature = Symbol.of("Feature", 1); 34 private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
34
35 private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
36 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
37
38 private static final Symbol<Boolean> features = Symbol.of("Features", 2); 35 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
39 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2); 36 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
40 37
41 private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel); 38 private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
42 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
43 private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
44 private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
45 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
46 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
47 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
48
49 39
50 @Test 40 @Test
51 @Disabled("This test is only for debugging purposes") 41 @Disabled("This test is only for debugging purposes")
52 void BFSTest() { 42 void BFSTest() {
53 var createClassPrecondition = Query.of("CreateClassPrecondition", 43 var createClassRule = Rule.of("CreateClass", (builder, model) -> builder
54 (builder, model) -> builder.clause( 44 .clause(
55 classModelView.call(model) 45 classModelView.call(model)
56 )); 46 )
57 47 .action((newClassElement) -> List.of(
58 var createClassRule = new TransformationRule("CreateClass", 48 create(newClassElement),
59 createClassPrecondition, 49 add(classElement, newClassElement),
60 (model) -> { 50 add(classes, model, newClassElement)
61 var classesInterpretation = model.getInterpretation(classes); 51 )));
62 var classElementInterpretation = model.getInterpretation(classElement); 52
63 return ((Tuple activation) -> { 53 var createFeatureRule = Rule.of("CreateFeature", (builder, model) -> builder
64 var dseAdapter = model.getAdapter(ModificationAdapter.class); 54 .clause(
65 var modelElement = activation.get(0);
66
67 var newClassElement = dseAdapter.createObject();
68 var newClassElementId = newClassElement.get(0);
69
70 classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
71 classElementInterpretation.put(Tuple.of(newClassElementId), true);
72 });
73 });
74
75 var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
76 (builder, model) -> builder.clause(
77 classModelView.call(model) 55 classModelView.call(model)
78 )); 56 )
79 57 .action((newFeature) -> List.of(
80 var createFeatureRule = new TransformationRule("CreateFeature", 58 create(newFeature),
81 createFeaturePrecondition, 59 add(feature, newFeature),
82 (model) -> { 60 add(features, model, newFeature)
83 var featuresInterpretation = model.getInterpretation(features); 61 )));
84 var featureInterpretation = model.getInterpretation(feature);
85 return ((Tuple activation) -> {
86 var dseAdapter = model.getAdapter(ModificationAdapter.class);
87 var modelElement = activation.get(0);
88
89 var newClassElement = dseAdapter.createObject();
90 var newClassElementId = newClassElement.get(0);
91
92 featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
93 featureInterpretation.put(Tuple.of(newClassElementId), true);
94 });
95 });
96 62
97 var store = ModelStore.builder() 63 var store = ModelStore.builder()
98 .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features) 64 .symbols(classModel, classElement, feature, classes, features)
99 .with(ViatraModelQueryAdapter.builder() 65 .with(ViatraModelQueryAdapter.builder())
100 .queries(createClassPrecondition, createFeaturePrecondition))
101 .with(ModelVisualizerAdapter.builder() 66 .with(ModelVisualizerAdapter.builder()
102 .withOutputpath("test_output") 67 .withOutputpath("test_output")
103 .withFormat(FileFormat.DOT) 68 .withFormat(FileFormat.DOT)
104 .withFormat(FileFormat.SVG) 69 .withFormat(FileFormat.SVG)
105 .saveStates() 70 .saveStates()
106 .saveDesignSpace() 71 .saveDesignSpace())
107 )
108 .with(StateCoderAdapter.builder()) 72 .with(StateCoderAdapter.builder())
109 .with(ModificationAdapter.builder()) 73 .with(ModificationAdapter.builder())
110 .with(DesignSpaceExplorationAdapter.builder() 74 .with(DesignSpaceExplorationAdapter.builder()
111 .transformations(createClassRule, createFeatureRule) 75 .transformations(createClassRule, createFeatureRule)
112 .objectives(new DummyRandomObjective()) 76 .objectives(new DummyRandomObjective())
113 .accept(new DummyRandomCriterion()) 77 .accept(new DummyRandomCriterion())
114 .exclude(new DummyRandomCriterion()) 78 .exclude(new DummyRandomCriterion()))
115 )
116 .build(); 79 .build();
117 80
118 var model = store.createEmptyModel(); 81 var model = store.createEmptyModel();
@@ -128,11 +91,9 @@ class DebugTest {
128 var initialVersion = model.commit(); 91 var initialVersion = model.commit();
129 queryEngine.flushChanges(); 92 queryEngine.flushChanges();
130 93
131
132 var bestFirst = new BestFirstStoreManager(store); 94 var bestFirst = new BestFirstStoreManager(store);
133 bestFirst.startExploration(initialVersion); 95 bestFirst.startExploration(initialVersion);
134 var resultStore = bestFirst.getSolutionStore(); 96 var resultStore = bestFirst.getSolutionStore();
135 System.out.println("states size: " + resultStore.getSolutions().size()); 97 System.out.println("states size: " + resultStore.getSolutions().size());
136
137 } 98 }
138} 99}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java
deleted file mode 100644
index f5f13433..00000000
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/DesignSpaceExplorationTest.java
+++ /dev/null
@@ -1,596 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import tools.refinery.store.query.view.AnySymbolView;
9import tools.refinery.store.query.view.KeyOnlyView;
10import tools.refinery.store.representation.Symbol;
11
12class DesignSpaceExplorationTest {
13// private static final Symbol<Boolean> namedElement = Symbol.of("NamedElement", 1);
14// private static final Symbol<Boolean> attribute = Symbol.of("Attribute", 1);
15// private static final Symbol<Boolean> method = Symbol.of("Method", 1);
16// private static final Symbol<Boolean> dataDependency = Symbol.of("DataDependency", 2);
17// private static final Symbol<Boolean> functionalDependency = Symbol.of("FunctionalDependency", 2);
18
19 private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
20 private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
21 private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
22
23 private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
24 private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
25
26 private static final Symbol<Boolean> features = Symbol.of("Features", 2);
27 private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
28
29 private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
30 private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
31 private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
32 private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
33 private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
34 private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
35 private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
36
37// @Test
38// void createObjectTest() {
39// var store = ModelStore.builder()
40// .with(ViatraModelQueryAdapter.builder())
41// .with(DesignSpaceExplorationAdapter.builder()
42// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
43// .build();
44//
45// var model = store.createEmptyModel();
46// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
47//
48// assertEquals(0, dseAdapter.getModelSize());
49//
50// var newModel = dseAdapter.createObject();
51// var newModelId = newModel.get(0);
52// var newClass1 = dseAdapter.createObject();
53// var newClass1Id = newClass1.get(0);
54// var newClass2 = dseAdapter.createObject();
55// var newClass2Id = newClass2.get(0);
56// var newField = dseAdapter.createObject();
57// var newFieldId = newField.get(0);
58//
59// assertEquals(0, newModelId);
60// assertEquals(1, newClass1Id);
61// assertEquals(2, newClass2Id);
62// assertEquals(3, newFieldId);
63// assertEquals(4, dseAdapter.getModelSize());
64// }
65
66// @Test
67// void deleteMiddleObjectTest() {
68// var store = ModelStore.builder()
69// .with(ViatraModelQueryAdapter.builder())
70// .with(DesignSpaceExplorationAdapter.builder()
71// .strategy(new DepthFirstStrategy()))
72// .build();
73//
74// var model = store.createEmptyModel();
75// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
76//
77// assertEquals(0, dseAdapter.getModelSize());
78//
79// var newObject0 = dseAdapter.createObject();
80// var newObject0Id = newObject0.get(0);
81// var newObject1 = dseAdapter.createObject();
82// var newObject1Id = newObject1.get(0);
83// var newObject2 = dseAdapter.createObject();
84// var newObject2Id = newObject2.get(0);
85// var newObject3 = dseAdapter.createObject();
86// var newObject3Id = newObject3.get(0);
87//
88// assertEquals(0, newObject0Id);
89// assertEquals(1, newObject1Id);
90// assertEquals(2, newObject2Id);
91// assertEquals(3, newObject3Id);
92// assertEquals(4, dseAdapter.getModelSize());
93//
94// dseAdapter.deleteObject(newObject1);
95// assertEquals(4, dseAdapter.getModelSize());
96//
97// var newObject4 = dseAdapter.createObject();
98// var newObject4Id = newObject4.get(0);
99// assertEquals(4, newObject4Id);
100// assertEquals(5, dseAdapter.getModelSize());
101//
102// dseAdapter.deleteObject(newObject4);
103// assertEquals(5, dseAdapter.getModelSize());
104// }
105//
106// @Test
107// void DFSTrivialTest() {
108// var store = ModelStore.builder()
109// .symbols(classModel)
110// .with(ViatraModelQueryAdapter.builder())
111// .with(DesignSpaceExplorationAdapter.builder()
112// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
113// .build();
114//
115// var model = store.createEmptyModel();
116// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
117//
118// var states = dseAdapter.explore();
119// assertEquals(1, states.size());
120// }
121//
122// @Test
123// void DFSOneRuleTest() {
124// var createClassPrecondition = Query.of("CreateClassPrecondition",
125// (builder, model) -> builder.clause(
126// classModelView.call(model)
127// ));
128//
129// var createClassRule = new TransformationRule("CreateClass",
130// createClassPrecondition,
131// (model) -> {
132// var classesInterpretation = model.getInterpretation(classes);
133// var classElementInterpretation = model.getInterpretation(classElement);
134// return ((Tuple activation) -> {
135// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
136// var modelElement = activation.get(0);
137//
138// var newClassElement = dseAdapter.createObject();
139// var newClassElementId = newClassElement.get(0);
140//
141// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
142// classElementInterpretation.put(Tuple.of(newClassElementId), true);
143// });
144// });
145//
146// var store = ModelStore.builder()
147// .symbols(classModel, classElement, classes)
148// .with(ViatraModelQueryAdapter.builder()
149// .queries(createClassPrecondition))
150// .with(DesignSpaceExplorationAdapter.builder()
151// .transformations(createClassRule)
152// .strategy(new DepthFirstStrategy().withDepthLimit(0)
153// ))
154// .build();
155//
156// var model = store.createEmptyModel();
157// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
158// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
159//
160// var modelElementInterpretation = model.getInterpretation(classModel);
161// modelElementInterpretation.put(dseAdapter.createObject(), true);
162// queryEngine.flushChanges();
163//
164// var states = dseAdapter.explore();
165// assertEquals(1, states.size());
166// }
167//
168// @Test
169// void DFSContinueTest() {
170// var createClassPrecondition = Query.of("CreateClassPrecondition",
171// (builder, model) -> builder.clause(
172// classModelView.call(model)
173// ));
174//
175// var createClassRule = new TransformationRule("CreateClass",
176// createClassPrecondition,
177// (model) -> {
178// var classesInterpretation = model.getInterpretation(classes);
179// var classElementInterpretation = model.getInterpretation(classElement);
180// return ((Tuple activation) -> {
181// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
182// var modelElement = activation.get(0);
183//
184// var newClassElement = dseAdapter.createObject();
185// var newClassElementId = newClassElement.get(0);
186//
187// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
188// classElementInterpretation.put(Tuple.of(newClassElementId), true);
189// });
190// });
191//
192// var store = ModelStore.builder()
193// .symbols(classModel, classElement, classes)
194// .with(ViatraModelQueryAdapter.builder()
195// .queries(createClassPrecondition))
196// .with(DesignSpaceExplorationAdapter.builder()
197// .transformations(createClassRule)
198// .strategy(new DepthFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
199// ))
200// .build();
201//
202// var model = store.createEmptyModel();
203// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
204// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
205//
206// var modelElementInterpretation = model.getInterpretation(classModel);
207// modelElementInterpretation.put(dseAdapter.createObject(), true);
208// queryEngine.flushChanges();
209//
210// var states = dseAdapter.explore();
211// assertEquals(5, states.size());
212// }
213//
214// @Test
215// void DFSCompletenessTest() {
216// var createClassPrecondition = Query.of("CreateClassPrecondition",
217// (builder, model) -> builder.clause(
218// classModelView.call(model)
219// ));
220//
221// var createClassRule = new TransformationRule("CreateClass",
222// createClassPrecondition,
223// (model) -> {
224// var classesInterpretation = model.getInterpretation(classes);
225// var classElementInterpretation = model.getInterpretation(classElement);
226// return ((Tuple activation) -> {
227// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
228// var modelElement = activation.get(0);
229//
230// var newClassElement = dseAdapter.createObject();
231// var newClassElementId = newClassElement.get(0);
232//
233// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
234// classElementInterpretation.put(Tuple.of(newClassElementId), true);
235// });
236// });
237//
238// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
239// (builder, model) -> builder.clause(
240// classModelView.call(model)
241// ));
242//
243// var createFeatureRule = new TransformationRule("CreateFeature",
244// createFeaturePrecondition,
245// (model) -> {
246// var featuresInterpretation = model.getInterpretation(features);
247// var featureInterpretation = model.getInterpretation(feature);
248// return ((Tuple activation) -> {
249// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
250// var modelElement = activation.get(0);
251//
252// var newClassElement = dseAdapter.createObject();
253// var newClassElementId = newClassElement.get(0);
254//
255// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
256// featureInterpretation.put(Tuple.of(newClassElementId), true);
257// });
258// });
259//
260// var store = ModelStore.builder()
261// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
262// .with(ViatraModelQueryAdapter.builder()
263// .queries(createClassPrecondition, createFeaturePrecondition))
264// .with(DesignSpaceExplorationAdapter.builder()
265// .transformations(createClassRule, createFeatureRule)
266// .strategy(new DepthFirstStrategy().withDepthLimit(10).continueIfHardObjectivesFulfilled()
267// ))
268// .build();
269//
270// var model = store.createEmptyModel();
271// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
272// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
273//
274// var modelElementInterpretation = model.getInterpretation(classModel);
275// modelElementInterpretation.put(dseAdapter.createObject(), true);
276// queryEngine.flushChanges();
277//
278// var states = dseAdapter.explore();
279// assertEquals(2047, states.size());
280// }
281//
282// @Test
283// void DFSSolutionLimitTest() {
284// var createClassPrecondition = Query.of("CreateClassPrecondition",
285// (builder, model) -> builder.clause(
286// classModelView.call(model)
287// ));
288//
289// var createClassRule = new TransformationRule("CreateClass",
290// createClassPrecondition,
291// (model) -> {
292// var classesInterpretation = model.getInterpretation(classes);
293// var classElementInterpretation = model.getInterpretation(classElement);
294// return ((Tuple activation) -> {
295// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
296// var modelElement = activation.get(0);
297//
298// var newClassElement = dseAdapter.createObject();
299// var newClassElementId = newClassElement.get(0);
300//
301// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
302// classElementInterpretation.put(Tuple.of(newClassElementId), true);
303// });
304// });
305//
306// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
307// (builder, model) -> builder.clause(
308// classModelView.call(model)
309// ));
310//
311// var createFeatureRule = new TransformationRule("CreateFeature",
312// createFeaturePrecondition,
313// (model) -> {
314// var featuresInterpretation = model.getInterpretation(features);
315// var featureInterpretation = model.getInterpretation(feature);
316// return ((Tuple activation) -> {
317// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
318// var modelElement = activation.get(0);
319//
320// var newClassElement = dseAdapter.createObject();
321// var newClassElementId = newClassElement.get(0);
322//
323// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
324// featureInterpretation.put(Tuple.of(newClassElementId), true);
325// });
326// });
327//
328// var store = ModelStore.builder()
329// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
330// .with(ViatraModelQueryAdapter.builder()
331// .queries(createClassPrecondition, createFeaturePrecondition))
332// .with(DesignSpaceExplorationAdapter.builder()
333// .transformations(createClassRule, createFeatureRule)
334// .strategy(new DepthFirstStrategy().withSolutionLimit(222)
335// .continueIfHardObjectivesFulfilled()
336// ))
337// .build();
338//
339// var model = store.createEmptyModel();
340// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
341// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
342//
343// var modelElementInterpretation = model.getInterpretation(classModel);
344// modelElementInterpretation.put(dseAdapter.createObject(), true);
345// queryEngine.flushChanges();
346//
347// var states = dseAdapter.explore();
348// assertEquals(222, states.size());
349// }
350//
351// @Test
352// void BeFSTrivialTest() {
353// var store = ModelStore.builder()
354// .symbols(classModel)
355// .with(ViatraModelQueryAdapter.builder())
356// .with(DesignSpaceExplorationAdapter.builder()
357// .strategy(new BestFirstStrategy().withDepthLimit(0)))
358// .build();
359//
360// var model = store.createEmptyModel();
361// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
362//
363// var states = dseAdapter.explore();
364// assertEquals(1, states.size());
365// }
366//
367// @Test
368// void BeFSOneRuleTest() {
369// var createClassPrecondition = Query.of("CreateClassPrecondition",
370// (builder, model) -> builder.clause(
371// classModelView.call(model)
372// ));
373//
374// var createClassRule = new TransformationRule("CreateClass",
375// createClassPrecondition,
376// (model) -> {
377// var classesInterpretation = model.getInterpretation(classes);
378// var classElementInterpretation = model.getInterpretation(classElement);
379// return ((Tuple activation) -> {
380// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
381// var modelElement = activation.get(0);
382//
383// var newClassElement = dseAdapter.createObject();
384// var newClassElementId = newClassElement.get(0);
385//
386// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
387// classElementInterpretation.put(Tuple.of(newClassElementId), true);
388// });
389// });
390//
391// var store = ModelStore.builder()
392// .symbols(classModel, classElement, classes)
393// .with(ViatraModelQueryAdapter.builder()
394// .queries(createClassPrecondition))
395// .with(DesignSpaceExplorationAdapter.builder()
396// .transformations(createClassRule)
397// .strategy(new BestFirstStrategy().withDepthLimit(4)
398// ))
399// .build();
400//
401// var model = store.createEmptyModel();
402// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
403// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
404//
405// var modelElementInterpretation = model.getInterpretation(classModel);
406// modelElementInterpretation.put(dseAdapter.createObject(), true);
407// queryEngine.flushChanges();
408//
409// var states = dseAdapter.explore();
410// assertEquals(1, states.size());
411// }
412//
413// @Test
414// void BeFSContinueTest() {
415// var createClassPrecondition = Query.of("CreateClassPrecondition",
416// (builder, model) -> builder.clause(
417// classModelView.call(model)
418// ));
419//
420// var createClassRule = new TransformationRule("CreateClass",
421// createClassPrecondition,
422// (model) -> {
423// var classesInterpretation = model.getInterpretation(classes);
424// var classElementInterpretation = model.getInterpretation(classElement);
425// return ((Tuple activation) -> {
426// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
427// var modelElement = activation.get(0);
428//
429// var newClassElement = dseAdapter.createObject();
430// var newClassElementId = newClassElement.get(0);
431//
432// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
433// classElementInterpretation.put(Tuple.of(newClassElementId), true);
434// });
435// });
436//
437// var store = ModelStore.builder()
438// .symbols(classModel, classElement, classes)
439// .with(ViatraModelQueryAdapter.builder()
440// .queries(createClassPrecondition))
441// .with(DesignSpaceExplorationAdapter.builder()
442// .transformations(createClassRule)
443// .strategy(new BestFirstStrategy().withDepthLimit(4).continueIfHardObjectivesFulfilled()
444// ))
445// .build();
446//
447// var model = store.createEmptyModel();
448// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
449// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
450//
451// var modelElementInterpretation = model.getInterpretation(classModel);
452// modelElementInterpretation.put(dseAdapter.createObject(), true);
453// queryEngine.flushChanges();
454//
455// var states = dseAdapter.explore();
456// assertEquals(5, states.size());
457// }
458//
459// @Test
460// void BeFSCompletenessTest() {
461// var createClassPrecondition = Query.of("CreateClassPrecondition",
462// (builder, model) -> builder.clause(
463// classModelView.call(model)
464// ));
465//
466// var createClassRule = new TransformationRule("CreateClass",
467// createClassPrecondition,
468// (model) -> {
469// var classesInterpretation = model.getInterpretation(classes);
470// var classElementInterpretation = model.getInterpretation(classElement);
471// return ((Tuple activation) -> {
472// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
473// var modelElement = activation.get(0);
474//
475// var newClassElement = dseAdapter.createObject();
476// var newClassElementId = newClassElement.get(0);
477//
478// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
479// classElementInterpretation.put(Tuple.of(newClassElementId), true);
480// });
481// });
482//
483// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
484// (builder, model) -> builder.clause(
485// classModelView.call(model)
486// ));
487//
488// var createFeatureRule = new TransformationRule("CreateFeature",
489// createFeaturePrecondition,
490// (model) -> {
491// var featuresInterpretation = model.getInterpretation(features);
492// var featureInterpretation = model.getInterpretation(feature);
493// return ((Tuple activation) -> {
494// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
495// var modelElement = activation.get(0);
496//
497// var newClassElement = dseAdapter.createObject();
498// var newClassElementId = newClassElement.get(0);
499//
500// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
501// featureInterpretation.put(Tuple.of(newClassElementId), true);
502// });
503// });
504//
505// var store = ModelStore.builder()
506// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
507// .with(ViatraModelQueryAdapter.builder()
508// .queries(createClassPrecondition, createFeaturePrecondition))
509// .with(DesignSpaceExplorationAdapter.builder()
510// .transformations(createClassRule, createFeatureRule)
511// .strategy(new BestFirstStrategy().withDepthLimit(10).continueIfHardObjectivesFulfilled()
512// ))
513// .build();
514//
515// var model = store.createEmptyModel();
516// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
517// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
518//
519// var modelElementInterpretation = model.getInterpretation(classModel);
520// modelElementInterpretation.put(dseAdapter.createObject(), true);
521// queryEngine.flushChanges();
522//
523// var states = dseAdapter.explore();
524// assertEquals(2047, states.size());
525// }
526//
527// @Test
528// void BeFSSolutionLimitTest() {
529// var createClassPrecondition = Query.of("CreateClassPrecondition",
530// (builder, model) -> builder.clause(
531// classModelView.call(model)
532// ));
533//
534// var createClassRule = new TransformationRule("CreateClass",
535// createClassPrecondition,
536// (model) -> {
537// var classesInterpretation = model.getInterpretation(classes);
538// var classElementInterpretation = model.getInterpretation(classElement);
539// return ((Tuple activation) -> {
540// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
541// var modelElement = activation.get(0);
542//
543// var newClassElement = dseAdapter.createObject();
544// var newClassElementId = newClassElement.get(0);
545//
546// classesInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
547// classElementInterpretation.put(Tuple.of(newClassElementId), true);
548// });
549// });
550//
551// var createFeaturePrecondition = Query.of("CreateFeaturePrecondition",
552// (builder, model) -> builder.clause(
553// classModelView.call(model)
554// ));
555//
556// var createFeatureRule = new TransformationRule("CreateFeature",
557// createFeaturePrecondition,
558// (model) -> {
559// var featuresInterpretation = model.getInterpretation(features);
560// var featureInterpretation = model.getInterpretation(feature);
561// return ((Tuple activation) -> {
562// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
563// var modelElement = activation.get(0);
564//
565// var newClassElement = dseAdapter.createObject();
566// var newClassElementId = newClassElement.get(0);
567//
568// featuresInterpretation.put(Tuple.of(modelElement, newClassElementId), true);
569// featureInterpretation.put(Tuple.of(newClassElementId), true);
570// });
571// });
572//
573// var store = ModelStore.builder()
574// .symbols(classModel, classElement, classes, feature, features, isEncapsulatedBy, encapsulates)
575// .with(ViatraModelQueryAdapter.builder()
576// .queries(createClassPrecondition, createFeaturePrecondition))
577// .with(DesignSpaceExplorationAdapter.builder()
578// .transformations(createClassRule, createFeatureRule)
579// .strategy(new BestFirstStrategy().withSolutionLimit(222)
580// .continueIfHardObjectivesFulfilled()
581// ))
582// .build();
583//
584// var model = store.createEmptyModel();
585// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
586// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
587//
588// var modelElementInterpretation = model.getInterpretation(classModel);
589// modelElementInterpretation.put(dseAdapter.createObject(), true);
590// queryEngine.flushChanges();
591//
592// var states = dseAdapter.explore();
593// assertEquals(222, states.size());
594// }
595
596}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java
deleted file mode 100644
index 43b04e0d..00000000
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/TransformationRuleTest.java
+++ /dev/null
@@ -1,414 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.dse;
7
8import org.junit.jupiter.api.Test;
9//
10//import tools.refinery.store.dse.transition.DesignSpaceExplorationAdapter;
11//import tools.refinery.store.model.ModelStore;
12//import tools.refinery.store.query.ModelQueryAdapter;
13//import tools.refinery.store.query.dnf.Query;
14//import tools.refinery.store.dse.transition.TransformationRule;
15//import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
16//import tools.refinery.store.query.view.AnySymbolView;
17//import tools.refinery.store.query.view.KeyOnlyView;
18//import tools.refinery.store.representation.Symbol;
19//import tools.refinery.store.tuple.Tuple;
20//
21//import java.util.List;
22//import java.util.Map;
23//
24//import static org.junit.jupiter.api.Assertions.assertEquals;
25//import static tools.refinery.store.query.literal.Literals.not;
26//import static tools.refinery.store.dse.tests.QueryAssertions.assertResults;
27//
28class TransformationRuleTest {
29//
30// private static final Symbol<Boolean> classModel = Symbol.of("ClassModel", 1);
31// private static final Symbol<Boolean> classElement = Symbol.of("ClassElement", 1);
32// private static final Symbol<Boolean> feature = Symbol.of("Feature", 1);
33//
34// private static final Symbol<Boolean> isEncapsulatedBy = Symbol.of("IsEncapsulatedBy", 2);
35// private static final Symbol<Boolean> encapsulates = Symbol.of("Encapsulates", 2);
36//
37// private static final Symbol<Boolean> features = Symbol.of("Features", 2);
38// private static final Symbol<Boolean> classes = Symbol.of("Classes", 2);
39//
40// private static final AnySymbolView classModelView = new KeyOnlyView<>(classModel);
41// private static final AnySymbolView classElementView = new KeyOnlyView<>(classElement);
42// private static final AnySymbolView featureView = new KeyOnlyView<>(feature);
43// private static final AnySymbolView isEncapsulatedByView = new KeyOnlyView<>(isEncapsulatedBy);
44// private static final AnySymbolView encapsulatesView = new KeyOnlyView<>(encapsulates);
45// private static final AnySymbolView featuresView = new KeyOnlyView<>(features);
46// private static final AnySymbolView classesView = new KeyOnlyView<>(classes);
47//
48// @Test
49// void activationsTest() {
50// var assignFeaturePreconditionHelper = Query.of("AssignFeaturePreconditionHelper",
51// (builder, model, c, f) -> builder.clause(
52// classElementView.call(c),
53// classesView.call(model, c),
54// encapsulatesView.call(c, f)
55// ));
56//
57// var assignFeaturePrecondition = Query.of("AssignFeaturePrecondition", (builder, c2, f)
58// -> builder.clause((model, c1) -> List.of(
59// classModelView.call(model),
60// featureView.call(f),
61// classElementView.call(c2),
62// featuresView.call(model, f),
63// classesView.call(model, c1),
64// not(assignFeaturePreconditionHelper.call(model, c2, f)),
65// not(encapsulatesView.call(c2, f))
66// )));
67//
68// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
69// (builder, model, c) -> builder.clause((f) -> List.of(
70// classModelView.call(model),
71// classElementView.call(c),
72// featuresView.call(model, f),
73// not(encapsulatesView.call(c, f))
74// )));
75//
76// TransformationRule assignFeatureRule = new TransformationRule("AssignFeature",
77// assignFeaturePrecondition,
78// (model) -> {
79// var isEncapsulatedByInterpretation = model.getInterpretation(isEncapsulatedBy);
80// return ((Tuple activation) -> {
81// var feature = activation.get(0);
82// var classElement = activation.get(1);
83//
84// isEncapsulatedByInterpretation.put(Tuple.of(feature, classElement), true);
85// });
86// });
87//
88// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
89// deleteEmptyClassPrecondition,
90// (model) -> {
91// var classesInterpretation = model.getInterpretation(classes);
92// var classElementInterpretation = model.getInterpretation(classElement);
93// return ((Tuple activation) -> {
94// var modelElement = activation.get(0);
95// var classElement = activation.get(1);
96//
97// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
98// classElementInterpretation.put(Tuple.of(classElement), false);
99// });
100// });
101//
102//
103// var store = ModelStore.builder()
104// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
105// .with(ViatraModelQueryAdapter.builder()
106// .queries(assignFeaturePrecondition, assignFeaturePreconditionHelper,
107// deleteEmptyClassPrecondition))
108// .with(DesignSpaceExplorationAdapter.builder()
109// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
110// .build();
111//
112// var model = store.createEmptyModel();
113// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
114// assignFeatureRule.prepare(model, queryEngine);
115// deleteEmptyClassRule.prepare(model, queryEngine);
116//
117// var classModelInterpretation = model.getInterpretation(classModel);
118// var classElementInterpretation = model.getInterpretation(classElement);
119// var featureInterpretation = model.getInterpretation(feature);
120// var featuresInterpretation = model.getInterpretation(features);
121// var classesInterpretation = model.getInterpretation(classes);
122//
123// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
124// var newModel = dseAdapter.createObject();
125// var newModelId = newModel.get(0);
126// var newClass1 = dseAdapter.createObject();
127// var newClass1Id = newClass1.get(0);
128// var newClass2 = dseAdapter.createObject();
129// var newClass2Id = newClass2.get(0);
130// var newField = dseAdapter.createObject();
131// var newFieldId = newField.get(0);
132//
133// classModelInterpretation.put(newModel, true);
134// classElementInterpretation.put(newClass1, true);
135// classElementInterpretation.put(newClass2, true);
136// featureInterpretation.put(newField, true);
137// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
138// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
139// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
140//
141// queryEngine.flushChanges();
142//
143// var assignFeatureRuleActivations = assignFeatureRule.getAllActivationsAsResultSet();
144// var deleteEmptyClassRuleActivations = deleteEmptyClassRule.getAllActivationsAsResultSet();
145//
146// assertResults(Map.of(
147// Tuple.of(newClass1Id, newFieldId), true,
148// Tuple.of(newClass2Id, newFieldId), true
149// ), assignFeatureRuleActivations);
150//
151// assertResults(Map.of(
152// Tuple.of(newModelId, newClass1Id), true,
153// Tuple.of(newModelId, newClass2Id), true
154// ), deleteEmptyClassRuleActivations);
155// }
156//
157// @Test
158// void randomActivationTest() {
159// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
160// (builder, model, c) -> builder.clause((f) -> List.of(
161// classModelView.call(model),
162// classElementView.call(c),
163// featuresView.call(model, f),
164// not(encapsulatesView.call(c, f))
165// )));
166//
167// TransformationRule deleteEmptyClassRule0 = new TransformationRule("DeleteEmptyClass0",
168// deleteEmptyClassPrecondition,
169// (model) -> {
170// var classesInterpretation = model.getInterpretation(classes);
171// var classElementInterpretation = model.getInterpretation(classElement);
172// return ((Tuple activation) -> {
173// var modelElement = activation.get(0);
174// var classElement = activation.get(1);
175//
176// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
177// classElementInterpretation.put(Tuple.of(classElement), false);
178// });
179// },
180// 0L);
181//
182// TransformationRule deleteEmptyClassRule1 = new TransformationRule("DeleteEmptyClass1",
183// deleteEmptyClassPrecondition,
184// (model) -> {
185// var classesInterpretation = model.getInterpretation(classes);
186// var classElementInterpretation = model.getInterpretation(classElement);
187// return ((Tuple activation) -> {
188// var modelElement = activation.get(0);
189// var classElement = activation.get(1);
190//
191// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
192// classElementInterpretation.put(Tuple.of(classElement), false);
193// });
194// },
195// 78634L);
196//
197// var store = ModelStore.builder()
198// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
199// .with(ViatraModelQueryAdapter.builder()
200// .queries(deleteEmptyClassPrecondition))
201// .with(DesignSpaceExplorationAdapter.builder()
202// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
203// .build();
204//
205// var model = store.createEmptyModel();
206// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
207// deleteEmptyClassRule0.prepare(model, queryEngine);
208// deleteEmptyClassRule1.prepare(model, queryEngine);
209//
210// var classModelInterpretation = model.getInterpretation(classModel);
211// var classElementInterpretation = model.getInterpretation(classElement);
212// var featureInterpretation = model.getInterpretation(feature);
213// var featuresInterpretation = model.getInterpretation(features);
214// var classesInterpretation = model.getInterpretation(classes);
215//
216// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
217// var newModel = dseAdapter.createObject();
218// var newModelId = newModel.get(0);
219// var newClass1 = dseAdapter.createObject();
220// var newClass1Id = newClass1.get(0);
221// var newClass2 = dseAdapter.createObject();
222// var newClass2Id = newClass2.get(0);
223// var newField = dseAdapter.createObject();
224// var newFieldId = newField.get(0);
225//
226// classModelInterpretation.put(newModel, true);
227// classElementInterpretation.put(newClass1, true);
228// classElementInterpretation.put(newClass2, true);
229// featureInterpretation.put(newField, true);
230// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
231// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
232// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
233//
234// queryEngine.flushChanges();
235//
236//
237// var activation0 = deleteEmptyClassRule0.getRandomActivation().activation();
238// var activation1 = deleteEmptyClassRule1.getRandomActivation().activation();
239//
240// assertResults(Map.of(
241// Tuple.of(newModelId, newClass1Id), true,
242// Tuple.of(newModelId, newClass2Id), true
243// ), deleteEmptyClassRule0.getAllActivationsAsResultSet());
244//
245// assertResults(Map.of(
246// Tuple.of(newModelId, newClass1Id), true,
247// Tuple.of(newModelId, newClass2Id), true
248// ), deleteEmptyClassRule1.getAllActivationsAsResultSet());
249//
250// assertEquals(Tuple.of(newModelId, newClass2Id), activation0);
251// assertEquals(Tuple.of(newModelId, newClass1Id), activation1);
252//
253// }
254//
255// @Test
256// void fireTest() {
257// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
258// (builder, model, c) -> builder.clause((f) -> List.of(
259// classModelView.call(model),
260// classElementView.call(c),
261// featuresView.call(model, f),
262// not(encapsulatesView.call(c, f))
263// )));
264//
265// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass",
266// deleteEmptyClassPrecondition,
267// (model) -> {
268// var classesInterpretation = model.getInterpretation(classes);
269// var classElementInterpretation = model.getInterpretation(classElement);
270// return ((Tuple activation) -> {
271// var modelElement = activation.get(0);
272// var classElement = activation.get(1);
273//
274// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
275// classElementInterpretation.put(Tuple.of(classElement), false);
276// });
277// });
278//
279// var store = ModelStore.builder()
280// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
281// .with(ViatraModelQueryAdapter.builder()
282// .queries(deleteEmptyClassPrecondition))
283// .with(DesignSpaceExplorationAdapter.builder()
284// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
285// .build();
286//
287// var model = store.createEmptyModel();
288// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
289// deleteEmptyClassRule.prepare(model, queryEngine);
290//
291// var classModelInterpretation = model.getInterpretation(classModel);
292// var classElementInterpretation = model.getInterpretation(classElement);
293// var featureInterpretation = model.getInterpretation(feature);
294// var featuresInterpretation = model.getInterpretation(features);
295// var classesInterpretation = model.getInterpretation(classes);
296//
297// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
298// var newModel = dseAdapter.createObject();
299// var newModelId = newModel.get(0);
300// var newClass1 = dseAdapter.createObject();
301// var newClass1Id = newClass1.get(0);
302// var newClass2 = dseAdapter.createObject();
303// var newClass2Id = newClass2.get(0);
304// var newField = dseAdapter.createObject();
305// var newFieldId = newField.get(0);
306//
307// classModelInterpretation.put(newModel, true);
308// classElementInterpretation.put(newClass1, true);
309// classElementInterpretation.put(newClass2, true);
310// featureInterpretation.put(newField, true);
311// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
312// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
313// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
314//
315// queryEngine.flushChanges();
316//
317// assertResults(Map.of(
318// Tuple.of(newModelId, newClass1Id), true,
319// Tuple.of(newModelId, newClass2Id), true
320// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
321//
322//
323// deleteEmptyClassRule.fireActivation(Tuple.of(0, 1));
324//
325// assertResults(Map.of(
326// Tuple.of(newModelId, newClass1Id), false,
327// Tuple.of(newModelId, newClass2Id), true
328// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
329// }
330//
331// @Test
332// void randomFireTest() {
333// var deleteEmptyClassPrecondition = Query.of("DeleteEmptyClassPrecondition",
334// (builder, model, c) -> builder.clause((f) -> List.of(
335// classModelView.call(model),
336// classElementView.call(c),
337// featuresView.call(model, f),
338// not(encapsulatesView.call(c, f))
339// )));
340//
341// TransformationRule deleteEmptyClassRule = new TransformationRule("DeleteEmptyClass0",
342// deleteEmptyClassPrecondition,
343// (model) -> {
344// var classesInterpretation = model.getInterpretation(classes);
345// var classElementInterpretation = model.getInterpretation(classElement);
346// return ((Tuple activation) -> {
347// var modelElement = activation.get(0);
348// var classElement = activation.get(1);
349//
350// classesInterpretation.put(Tuple.of(modelElement, classElement), false);
351// classElementInterpretation.put(Tuple.of(classElement), false);
352// });
353// },
354// 0L);
355//
356// var store = ModelStore.builder()
357// .symbols(classModel, classElement, feature, isEncapsulatedBy, encapsulates, classes, features)
358// .with(ViatraModelQueryAdapter.builder()
359// .queries(deleteEmptyClassPrecondition))
360// .with(DesignSpaceExplorationAdapter.builder()
361// .strategy(new DepthFirstStrategy().withDepthLimit(0)))
362// .build();
363//
364// var model = store.createEmptyModel();
365// var queryEngine = model.getAdapter(ModelQueryAdapter.class);
366// deleteEmptyClassRule.prepare(model, queryEngine);
367//
368// var classModelInterpretation = model.getInterpretation(classModel);
369// var classElementInterpretation = model.getInterpretation(classElement);
370// var featureInterpretation = model.getInterpretation(feature);
371// var featuresInterpretation = model.getInterpretation(features);
372// var classesInterpretation = model.getInterpretation(classes);
373//
374// var dseAdapter = model.getAdapter(DesignSpaceExplorationAdapter.class);
375// var newModel = dseAdapter.createObject();
376// var newModelId = newModel.get(0);
377// var newClass1 = dseAdapter.createObject();
378// var newClass1Id = newClass1.get(0);
379// var newClass2 = dseAdapter.createObject();
380// var newClass2Id = newClass2.get(0);
381// var newField = dseAdapter.createObject();
382// var newFieldId = newField.get(0);
383//
384// classModelInterpretation.put(newModel, true);
385// classElementInterpretation.put(newClass1, true);
386// classElementInterpretation.put(newClass2, true);
387// featureInterpretation.put(newField, true);
388// classesInterpretation.put(Tuple.of(newModelId, newClass1Id), true);
389// classesInterpretation.put(Tuple.of(newModelId, newClass2Id), true);
390// featuresInterpretation.put(Tuple.of(newModelId, newFieldId), true);
391//
392// queryEngine.flushChanges();
393//
394// assertResults(Map.of(
395// Tuple.of(newModelId, newClass1Id), true,
396// Tuple.of(newModelId, newClass2Id), true
397// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
398//
399// deleteEmptyClassRule.fireRandomActivation();
400//
401// assertResults(Map.of(
402// Tuple.of(newModelId, newClass1Id), true,
403// Tuple.of(newModelId, newClass2Id), false
404// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
405//
406// deleteEmptyClassRule.fireRandomActivation();
407//
408// assertResults(Map.of(
409// Tuple.of(newModelId, newClass1Id), false,
410// Tuple.of(newModelId, newClass2Id), false
411// ), deleteEmptyClassRule.getAllActivationsAsResultSet());
412//
413// }
414}
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java
index be514eaf..f0a20720 100644
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/tests/QueryAssertions.java
@@ -30,7 +30,7 @@ public final class QueryAssertions {
30 } 30 }
31 31
32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) { 32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) {
33 var defaultValue = resultSet.getQuery().defaultValue(); 33 var defaultValue = resultSet.getCanonicalQuery().defaultValue();
34 var filteredExpected = new LinkedHashMap<Tuple, T>(); 34 var filteredExpected = new LinkedHashMap<Tuple, T>();
35 var executables = new ArrayList<Executable>(); 35 var executables = new ArrayList<Executable>();
36 for (var entry : expected.entrySet()) { 36 for (var entry : expected.entrySet()) {
diff --git a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/TransitionTests.java b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/TransitionTests.java
index b89360cb..42a0f89b 100644
--- a/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/TransitionTests.java
+++ b/subprojects/store-dse/src/test/java/tools/refinery/store/dse/transition/TransitionTests.java
@@ -7,16 +7,14 @@ package tools.refinery.store.dse.transition;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.dse.modification.ModificationAdapter; 9import tools.refinery.store.dse.modification.ModificationAdapter;
10import tools.refinery.store.dse.transition.objectives.QueryCriteria; 10import tools.refinery.store.dse.transition.objectives.Criteria;
11import tools.refinery.store.dse.transition.objectives.QueryObjective; 11import tools.refinery.store.dse.transition.objectives.Objectives;
12import tools.refinery.store.model.Model; 12import tools.refinery.store.model.Model;
13import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
14import tools.refinery.store.query.ModelQueryAdapter; 14import tools.refinery.store.query.ModelQueryAdapter;
15import tools.refinery.store.query.dnf.FunctionalQuery; 15import tools.refinery.store.query.dnf.FunctionalQuery;
16import tools.refinery.store.query.dnf.Query; 16import tools.refinery.store.query.dnf.Query;
17import tools.refinery.store.query.dnf.RelationalQuery; 17import tools.refinery.store.query.dnf.RelationalQuery;
18import tools.refinery.store.query.literal.CallPolarity;
19import tools.refinery.store.query.literal.Literals;
20import tools.refinery.store.query.term.Variable; 18import tools.refinery.store.query.term.Variable;
21import tools.refinery.store.query.term.int_.IntTerms; 19import tools.refinery.store.query.term.int_.IntTerms;
22import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 20import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
@@ -29,33 +27,33 @@ import tools.refinery.store.tuple.Tuple;
29import java.util.List; 27import java.util.List;
30 28
31import static org.junit.jupiter.api.Assertions.*; 29import static org.junit.jupiter.api.Assertions.*;
30import static tools.refinery.store.query.literal.Literals.check;
31import static tools.refinery.store.query.literal.Literals.not;
32 32
33class TransitionBuildTests { 33class TransitionBuildTests {
34 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false); 34 Symbol<Boolean> person = new Symbol<>("Person", 1, Boolean.class, false);
35 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false); 35 Symbol<Boolean> friend = new Symbol<>("friend", 2, Boolean.class, false);
36
36 AnySymbolView personView = new KeyOnlyView<>(person); 37 AnySymbolView personView = new KeyOnlyView<>(person);
37 AnySymbolView friendView = new KeyOnlyView<>(friend); 38 AnySymbolView friendView = new KeyOnlyView<>(friend);
38 39
39 /*RelationalQuery areNotFriends = Query.of("areNotFriends", 40 RelationalQuery moreThan3Friends = Query.of("moreThan3Friends", (builder, tooMuchFriends) -> builder
40 (builder, p1, p2) -> builder.clause( 41 .clause(Integer.class, (numberOfFriends) -> List.of(
41 personView.call(p1),
42 personView.call(p2),
43 not(friendView.call(p1, p2))))*/
44
45 RelationalQuery moreThan3Friends = Query.of("moreThan3Friends",
46 (builder, tooMuchFriends) -> builder.clause(Integer.class, (numberOfFriends) -> List.of(
47 numberOfFriends.assign(friendView.count(tooMuchFriends, Variable.of())), 42 numberOfFriends.assign(friendView.count(tooMuchFriends, Variable.of())),
48 Literals.assume(IntTerms.less(IntTerms.constant(3), numberOfFriends)), 43 check(IntTerms.less(IntTerms.constant(3), numberOfFriends)),
49 personView.call(tooMuchFriends) 44 personView.call(tooMuchFriends)
50 ))); 45 )));
51 46
52 RelationalQuery somebodyHasNoFriend = Query.of("somebodyHasNoFriend", 47 RelationalQuery somebodyHasNoFriend = Query.of("somebodyHasNoFriend", (builder, lonely) -> builder
53 (builder, lonely) -> builder.clause( 48 .clause(
54 personView.call(lonely), 49 personView.call(lonely),
55 friendView.call(CallPolarity.NEGATIVE, lonely, Variable.of()) 50 not(friendView.call(lonely, Variable.of()))
51 ));
52
53 FunctionalQuery<Integer> numberOfFriends = Query.of(Integer.class, (builder, output) -> builder
54 .clause(
55 output.assign(friendView.count(Variable.of(), Variable.of()))
56 )); 56 ));
57 FunctionalQuery<Integer> numberOfFriends = FunctionalQuery.of(Integer.class,
58 (builder, output) -> builder.clause(output.assign(friendView.count(Variable.of(), Variable.of()))));
59 57
60 @Test 58 @Test
61 void acceptTest() { 59 void acceptTest() {
@@ -141,9 +139,9 @@ class TransitionBuildTests {
141 .with(StateCoderAdapter.builder()) 139 .with(StateCoderAdapter.builder())
142 .with(ModificationAdapter.builder()) 140 .with(ModificationAdapter.builder())
143 .with(DesignSpaceExplorationAdapter.builder() 141 .with(DesignSpaceExplorationAdapter.builder()
144 .objective(new QueryObjective(numberOfFriends)) 142 .objective(Objectives.value(numberOfFriends))
145 .exclude(new QueryCriteria(moreThan3Friends, true)) 143 .exclude(Criteria.whenHasMatch(moreThan3Friends))
146 .accept(new QueryCriteria(somebodyHasNoFriend, false))) 144 .accept(Criteria.whenNoMatch(somebodyHasNoFriend)))
147 .build(); 145 .build();
148 146
149 return store.createEmptyModel(); 147 return store.createEmptyModel();
diff --git a/subprojects/store-query-viatra/build.gradle.kts b/subprojects/store-query-viatra/build.gradle.kts
index ef73e10a..fa1c1da3 100644
--- a/subprojects/store-query-viatra/build.gradle.kts
+++ b/subprojects/store-query-viatra/build.gradle.kts
@@ -9,9 +9,11 @@ plugins {
9} 9}
10 10
11dependencies { 11dependencies {
12 implementation(libs.ecore) 12 api(project(":refinery-viatra-runtime"))
13 api(libs.viatra) 13 api(project(":refinery-viatra-runtime-localsearch"))
14 api(project(":refinery-viatra-runtime-rete"))
15 api(project(":refinery-viatra-runtime-rete-recipes"))
14 api(project(":refinery-store-query")) 16 api(project(":refinery-store-query"))
15 api(project(":refinery-store-reasoning")) 17 implementation(libs.ecore)
16 api(project(":refinery-visualization")) 18 implementation(libs.slf4j.log4j)
17} 19}
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 931a07aa..6b3be115 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
@@ -5,13 +5,15 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
9import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
10import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
11import tools.refinery.store.model.ModelStore; 8import tools.refinery.store.model.ModelStore;
9import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.AnyQuery; 10import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
14import tools.refinery.store.query.ModelQueryBuilder; 12import tools.refinery.store.query.rewriter.DnfRewriter;
13import tools.refinery.viatra.runtime.CancellationToken;
14import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions;
15import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
16import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
15 17
16import java.util.Collection; 18import java.util.Collection;
17import java.util.function.Function; 19import java.util.function.Function;
@@ -28,6 +30,8 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder {
28 30
29 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); 31 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory);
30 32
33 ViatraModelQueryBuilder cancellationToken(CancellationToken cancellationToken);
34
31 @Override 35 @Override
32 default ViatraModelQueryBuilder queries(AnyQuery... queries) { 36 default ViatraModelQueryBuilder queries(AnyQuery... queries) {
33 ModelQueryBuilder.super.queries(queries); 37 ModelQueryBuilder.super.queries(queries);
@@ -43,12 +47,11 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder {
43 @Override 47 @Override
44 ViatraModelQueryBuilder query(AnyQuery query); 48 ViatraModelQueryBuilder query(AnyQuery query);
45 49
46 ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint); 50 @Override
51 ViatraModelQueryBuilder rewriter(DnfRewriter rewriter);
47 52
48 ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint); 53 ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint);
49 54
50 ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint);
51
52 @Override 55 @Override
53 ViatraModelQueryStoreAdapter build(ModelStore store); 56 ViatraModelQueryStoreAdapter build(ModelStore store);
54} 57}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
index da6d7bd5..588c00d4 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryStoreAdapter.java
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 8import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.ModelQueryStoreAdapter; 10import tools.refinery.store.query.ModelQueryStoreAdapter;
11 11
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
index d1a65a89..9303cae6 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/RelationalScope.java
@@ -6,10 +6,10 @@
6package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
7 7
8import org.apache.log4j.Logger; 8import org.apache.log4j.Logger;
9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; 9import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
10import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; 10import tools.refinery.viatra.runtime.api.scope.IEngineContext;
11import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; 11import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener;
12import org.eclipse.viatra.query.runtime.api.scope.QueryScope; 12import tools.refinery.viatra.runtime.api.scope.QueryScope;
13import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; 13import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext;
14 14
15public class RelationalScope extends QueryScope { 15public class RelationalScope extends QueryScope {
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 5f3e86b4..ad754988 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
@@ -5,69 +5,47 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
7 7
8import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine;
9import org.eclipse.viatra.query.runtime.api.GenericQueryGroup;
10import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
11import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl;
12import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
13import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
14import tools.refinery.store.model.Model; 8import tools.refinery.store.model.Model;
15import tools.refinery.store.model.ModelListener; 9import tools.refinery.store.model.ModelListener;
16import tools.refinery.store.query.resultset.AnyResultSet;
17import tools.refinery.store.query.resultset.EmptyResultSet;
18import tools.refinery.store.query.resultset.ResultSet;
19import tools.refinery.store.query.dnf.AnyQuery; 10import tools.refinery.store.query.dnf.AnyQuery;
20import tools.refinery.store.query.dnf.FunctionalQuery; 11import tools.refinery.store.query.dnf.FunctionalQuery;
21import tools.refinery.store.query.dnf.Query; 12import tools.refinery.store.query.dnf.Query;
22import tools.refinery.store.query.dnf.RelationalQuery; 13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.resultset.AnyResultSet;
15import tools.refinery.store.query.resultset.EmptyResultSet;
16import tools.refinery.store.query.resultset.ResultSet;
23import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 17import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
24import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher; 18import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher;
25import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; 19import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
26import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher; 20import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher;
21import tools.refinery.viatra.runtime.CancellationToken;
22import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine;
23import tools.refinery.viatra.runtime.api.GenericQueryGroup;
24import tools.refinery.viatra.runtime.api.IQuerySpecification;
27 25
28import java.lang.invoke.MethodHandle;
29import java.lang.invoke.MethodHandles;
30import java.util.Collection;
31import java.util.Collections; 26import java.util.Collections;
32import java.util.LinkedHashMap; 27import java.util.LinkedHashMap;
33import java.util.Map; 28import java.util.Map;
34 29
35public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener { 30public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener {
36 private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery";
37 private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE;
38 private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends";
39 private static final MethodHandle GET_QUERY_BACKENDS_HANDLE;
40
41 private final Model model; 31 private final Model model;
42 private final ViatraModelQueryStoreAdapterImpl storeAdapter; 32 private final ViatraModelQueryStoreAdapterImpl storeAdapter;
43 private final ViatraQueryEngineImpl queryEngine; 33 private final AdvancedViatraQueryEngine queryEngine;
44 private final Map<AnyQuery, AnyResultSet> resultSets; 34 private final Map<AnyQuery, AnyResultSet> resultSets;
45 private boolean pendingChanges; 35 private boolean pendingChanges;
46 36
47 static {
48 try {
49 var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup());
50 SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class,
51 DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE);
52 GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME,
53 Map.class);
54 } catch (IllegalAccessException | NoSuchFieldException e) {
55 throw new IllegalStateException("Cannot access private members of %s"
56 .formatted(ViatraQueryEngineImpl.class.getName()), e);
57 }
58 }
59
60 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { 37 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) {
61 this.model = model; 38 this.model = model;
62 this.storeAdapter = storeAdapter; 39 this.storeAdapter = storeAdapter;
63 var scope = new RelationalScope(this); 40 var scope = new RelationalScope(this);
64 queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope, 41 queryEngine = AdvancedViatraQueryEngine.createUnmanagedEngine(scope,
65 storeAdapter.getEngineOptions()); 42 storeAdapter.getEngineOptions());
66 43
67 var querySpecifications = storeAdapter.getQuerySpecifications(); 44 var querySpecifications = storeAdapter.getQuerySpecifications();
68 GenericQueryGroup.of( 45 GenericQueryGroup.of(
69 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() 46 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream()
70 ).prepare(queryEngine); 47 ).prepare(queryEngine);
48 queryEngine.flushChanges();
71 var vacuousQueries = storeAdapter.getVacuousQueries(); 49 var vacuousQueries = storeAdapter.getVacuousQueries();
72 resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size()); 50 resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size());
73 for (var entry : querySpecifications.entrySet()) { 51 for (var entry : querySpecifications.entrySet()) {
@@ -79,7 +57,6 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod
79 resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery)); 57 resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery));
80 } 58 }
81 59
82 setUpdatePropagationDelayed(true);
83 model.addListener(this); 60 model.addListener(this);
84 } 61 }
85 62
@@ -95,30 +72,6 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod
95 } 72 }
96 } 73 }
97 74
98 private void setUpdatePropagationDelayed(boolean value) {
99 try {
100 SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value);
101 } catch (Error e) {
102 // Fatal JVM errors should not be wrapped.
103 throw e;
104 } catch (Throwable e) {
105 throw new IllegalStateException("Cannot set %s".formatted(DELAY_MESSAGE_DELIVERY_FIELD_NAME), e);
106 }
107 }
108
109 private Collection<IQueryBackend> getQueryBackends() {
110 try {
111 @SuppressWarnings("unchecked")
112 var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine);
113 return backendMap.values();
114 } catch (Error e) {
115 // Fatal JVM errors should not be wrapped.
116 throw e;
117 } catch (Throwable e) {
118 throw new IllegalStateException("Cannot get %s".formatted(QUERY_BACKENDS_FIELD_NAME), e);
119 }
120 }
121
122 @Override 75 @Override
123 public Model getModel() { 76 public Model getModel() {
124 return model; 77 return model;
@@ -129,9 +82,14 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod
129 return storeAdapter; 82 return storeAdapter;
130 } 83 }
131 84
85 public CancellationToken getCancellationToken() {
86 return storeAdapter.getCancellationToken();
87 }
88
132 @Override 89 @Override
133 public <T> ResultSet<T> getResultSet(Query<T> query) { 90 public <T> ResultSet<T> getResultSet(Query<T> query) {
134 var resultSet = resultSets.get(query); 91 var canonicalQuery = storeAdapter.getCanonicalQuery(query);
92 var resultSet = resultSets.get(canonicalQuery);
135 if (resultSet == null) { 93 if (resultSet == null) {
136 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); 94 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name()));
137 } 95 }
@@ -153,20 +111,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod
153 111
154 @Override 112 @Override
155 public void flushChanges() { 113 public void flushChanges() {
156 if (!queryEngine.isUpdatePropagationDelayed()) { 114 queryEngine.flushChanges();
157 throw new IllegalStateException("Trying to flush changes while changes are already being flushed");
158 }
159 if (!pendingChanges) {
160 return;
161 }
162 setUpdatePropagationDelayed(false);
163 try {
164 for (var queryBackend : getQueryBackends()) {
165 queryBackend.flushUpdates();
166 }
167 } finally {
168 setUpdatePropagationDelayed(true);
169 }
170 pendingChanges = false; 115 pendingChanges = false;
171 } 116 }
172 117
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 ce2467b4..bb0630f3 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
@@ -5,22 +5,26 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
7 7
8import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions;
11import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
12import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
13import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory;
14import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
15import tools.refinery.store.model.ModelStore; 9import tools.refinery.store.model.ModelStore;
16import tools.refinery.store.model.ModelStoreBuilder;
17import tools.refinery.store.query.dnf.AnyQuery; 10import tools.refinery.store.query.dnf.AnyQuery;
18import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.rewriter.CompositeRewriter;
13import tools.refinery.store.query.rewriter.DnfRewriter;
14import tools.refinery.store.query.rewriter.DuplicateDnfRemover;
15import tools.refinery.store.query.rewriter.InputParameterResolver;
19import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; 16import tools.refinery.store.query.viatra.ViatraModelQueryBuilder;
20import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction; 17import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction;
21import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory;
22import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; 18import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
23import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery; 19import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery;
20import tools.refinery.viatra.runtime.CancellationToken;
21import tools.refinery.viatra.runtime.api.IQuerySpecification;
22import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions;
23import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory;
24import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions;
25import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
26import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
27import tools.refinery.viatra.runtime.rete.matcher.ReteBackendFactory;
24 28
25import java.util.*; 29import java.util.*;
26import java.util.function.Function; 30import java.util.function.Function;
@@ -32,15 +36,19 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder<Via
32 // Use a cost function that ignores the initial (empty) model but allows higher arity input keys. 36 // Use a cost function that ignores the initial (empty) model but allows higher arity input keys.
33 LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction() 37 LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction()
34 ), (IQueryBackendFactory) null); 38 ), (IQueryBackendFactory) null);
39 private CancellationToken cancellationToken = CancellationToken.NONE;
40 private final CompositeRewriter rewriter;
35 private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery(); 41 private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery();
36 private final Set<AnyQuery> vacuousQueries = new LinkedHashSet<>(); 42 private final Set<AnyQuery> queries = new LinkedHashSet<>();
37 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>();
38 43
39 public ViatraModelQueryBuilderImpl() { 44 public ViatraModelQueryBuilderImpl() {
40 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() 45 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder()
41 .withDefaultBackend(ReteBackendFactory.INSTANCE) 46 .withDefaultBackend(ReteBackendFactory.INSTANCE)
42 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) 47 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE)
43 .withDefaultSearchBackend(RelationalLocalSearchBackendFactory.INSTANCE); 48 .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE);
49 rewriter = new CompositeRewriter();
50 rewriter.addFirst(new DuplicateDnfRemover());
51 rewriter.addFirst(new InputParameterResolver());
44 } 52 }
45 53
46 @Override 54 @Override
@@ -79,58 +87,64 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder<Via
79 } 87 }
80 88
81 @Override 89 @Override
82 public ViatraModelQueryBuilder query(AnyQuery query) { 90 public ViatraModelQueryBuilder cancellationToken(CancellationToken cancellationToken) {
83 checkNotConfigured(); 91 this.cancellationToken = cancellationToken;
84 if (querySpecifications.containsKey(query) || vacuousQueries.contains(query)) {
85 // Ignore duplicate queries.
86 return this;
87 }
88 var dnf = query.getDnf();
89 var reduction = dnf.getReduction();
90 switch (reduction) {
91 case NOT_REDUCIBLE -> {
92 var pQuery = dnf2PQuery.translate(dnf);
93 querySpecifications.put(query, pQuery.build());
94 }
95 case ALWAYS_FALSE -> vacuousQueries.add(query);
96 case ALWAYS_TRUE -> throw new IllegalArgumentException(
97 "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name()));
98 default -> throw new IllegalArgumentException("Unknown reduction: " + reduction);
99 }
100 return this; 92 return this;
101 } 93 }
102 94
103 @Override 95 @Override
104 public ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint) { 96 public ViatraModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
105 hint(query.getDnf(), queryEvaluationHint); 97 checkNotConfigured();
106 query(query); 98 this.queries.addAll(queries);
107 return this; 99 return this;
108 } 100 }
109 101
110 @Override 102 @Override
111 public ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) { 103 public ViatraModelQueryBuilder query(AnyQuery query) {
112 checkNotConfigured(); 104 checkNotConfigured();
113 dnf2PQuery.setComputeHint(computeHint); 105 queries.add(query);
114 return this; 106 return this;
115 } 107 }
116 108
117 @Override 109 @Override
118 public ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint) { 110 public ViatraModelQueryBuilder rewriter(DnfRewriter rewriter) {
119 checkNotConfigured(); 111 this.rewriter.addFirst(rewriter);
120 dnf2PQuery.hint(dnf, queryEvaluationHint);
121 return this; 112 return this;
122 } 113 }
123 114
124 @Override 115 @Override
125 public void doConfigure(ModelStoreBuilder storeBuilder) { 116 public ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
126 dnf2PQuery.assertNoUnusedHints(); 117 checkNotConfigured();
118 dnf2PQuery.setComputeHint(computeHint);
119 return this;
127 } 120 }
128 121
129 @Override 122 @Override
130 public ViatraModelQueryStoreAdapterImpl doBuild(ModelStore store) { 123 public ViatraModelQueryStoreAdapterImpl doBuild(ModelStore store) {
124 var canonicalQueryMap = new HashMap<AnyQuery, AnyQuery>();
125 var querySpecifications = new LinkedHashMap<AnyQuery, IQuerySpecification<RawPatternMatcher>>();
126 var vacuousQueries = new LinkedHashSet<AnyQuery>();
127 for (var query : queries) {
128 var canonicalQuery = rewriter.rewrite(query);
129 canonicalQueryMap.put(query, canonicalQuery);
130 var dnf = canonicalQuery.getDnf();
131 var reduction = dnf.getReduction();
132 switch (reduction) {
133 case NOT_REDUCIBLE -> {
134 var pQuery = dnf2PQuery.translate(dnf);
135 querySpecifications.put(canonicalQuery, pQuery.build());
136 }
137 case ALWAYS_FALSE -> vacuousQueries.add(canonicalQuery);
138 case ALWAYS_TRUE -> throw new IllegalArgumentException(
139 "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name()));
140 default -> throw new IllegalArgumentException("Unknown reduction: " + reduction);
141 }
142 }
143
131 validateSymbols(store); 144 validateSymbols(store);
132 return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getSymbolViews(), 145 return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getSymbolViews(),
133 Collections.unmodifiableMap(querySpecifications), Collections.unmodifiableSet(vacuousQueries)); 146 Collections.unmodifiableMap(canonicalQueryMap), Collections.unmodifiableMap(querySpecifications),
147 Collections.unmodifiableSet(vacuousQueries), cancellationToken);
134 } 148 }
135 149
136 private ViatraQueryEngineOptions buildEngineOptions() { 150 private ViatraQueryEngineOptions buildEngineOptions() {
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 11a3c7fd..f32e1cc6 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,12 +5,14 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal; 6package tools.refinery.store.query.viatra.internal;
7 7
8import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 8import tools.refinery.viatra.runtime.CancellationToken;
9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 9import tools.refinery.viatra.runtime.api.IQuerySpecification;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 10import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions;
11import tools.refinery.viatra.runtime.matchers.context.IInputKey;
11import tools.refinery.store.model.Model; 12import tools.refinery.store.model.Model;
12import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.query.dnf.AnyQuery; 14import tools.refinery.store.query.dnf.AnyQuery;
15import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; 16import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter;
15import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; 17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
16import tools.refinery.store.query.view.AnySymbolView; 18import tools.refinery.store.query.view.AnySymbolView;
@@ -21,19 +23,24 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd
21 private final ModelStore store; 23 private final ModelStore store;
22 private final ViatraQueryEngineOptions engineOptions; 24 private final ViatraQueryEngineOptions engineOptions;
23 private final Map<AnySymbolView, IInputKey> inputKeys; 25 private final Map<AnySymbolView, IInputKey> inputKeys;
26 private final Map<AnyQuery, AnyQuery> canonicalQueryMap;
24 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications; 27 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications;
25 private final Set<AnyQuery> vacuousQueries; 28 private final Set<AnyQuery> vacuousQueries;
26 private final Set<AnyQuery> allQueries; 29 private final Set<AnyQuery> allQueries;
30 private final CancellationToken cancellationToken;
27 31
28 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, 32 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions,
29 Map<AnySymbolView, IInputKey> inputKeys, 33 Map<AnySymbolView, IInputKey> inputKeys,
34 Map<AnyQuery, AnyQuery> canonicalQueryMap,
30 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications, 35 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications,
31 Set<AnyQuery> vacuousQueries) { 36 Set<AnyQuery> vacuousQueries, CancellationToken cancellationToken) {
32 this.store = store; 37 this.store = store;
33 this.engineOptions = engineOptions; 38 this.engineOptions = engineOptions;
34 this.inputKeys = inputKeys; 39 this.inputKeys = inputKeys;
40 this.canonicalQueryMap = canonicalQueryMap;
35 this.querySpecifications = querySpecifications; 41 this.querySpecifications = querySpecifications;
36 this.vacuousQueries = vacuousQueries; 42 this.vacuousQueries = vacuousQueries;
43 this.cancellationToken = cancellationToken;
37 var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size()); 44 var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size());
38 mutableAllQueries.addAll(querySpecifications.keySet()); 45 mutableAllQueries.addAll(querySpecifications.keySet());
39 mutableAllQueries.addAll(vacuousQueries); 46 mutableAllQueries.addAll(vacuousQueries);
@@ -58,6 +65,21 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd
58 return allQueries; 65 return allQueries;
59 } 66 }
60 67
68 public CancellationToken getCancellationToken() {
69 return cancellationToken;
70 }
71
72 @Override
73 public <T> Query<T> getCanonicalQuery(Query<T> query) {
74 // We know that canonical forms of queries do not change output types.
75 @SuppressWarnings("unchecked")
76 var canonicalQuery = (Query<T>) canonicalQueryMap.get(query);
77 if (canonicalQuery == null) {
78 throw new IllegalArgumentException("Unknown query: " + query);
79 }
80 return canonicalQuery;
81 }
82
61 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { 83 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() {
62 return querySpecifications; 84 return querySpecifications;
63 } 85 }
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
index 8cb199d2..64d98258 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/DummyBaseIndexer.java
@@ -5,16 +5,16 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
7 7
8import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import tools.refinery.viatra.runtime.api.scope.IBaseIndex;
9import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; 9import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener;
10import org.eclipse.viatra.query.runtime.api.scope.IInstanceObserver; 10import tools.refinery.viatra.runtime.api.scope.IInstanceObserver;
11import org.eclipse.viatra.query.runtime.api.scope.ViatraBaseIndexChangeListener; 11import tools.refinery.viatra.runtime.api.scope.ViatraBaseIndexChangeListener;
12 12
13import java.lang.reflect.InvocationTargetException; 13import java.lang.reflect.InvocationTargetException;
14import java.util.concurrent.Callable; 14import java.util.concurrent.Callable;
15 15
16/** 16/**
17 * Copied from <code>org.eclipse.viatra.query.runtime.tabular.TabularEngineContext</code> 17 * Copied from <code>tools.refinery.viatra.runtime.tabular.TabularEngineContext</code>
18 */ 18 */
19public class DummyBaseIndexer implements IBaseIndex { 19public class DummyBaseIndexer implements IBaseIndex {
20 DummyBaseIndexer() { 20 DummyBaseIndexer() {
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
index 7220f8ca..f7d323ff 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalEngineContext.java
@@ -5,9 +5,9 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
7 7
8import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; 8import tools.refinery.viatra.runtime.api.scope.IBaseIndex;
9import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; 9import tools.refinery.viatra.runtime.api.scope.IEngineContext;
10import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; 10import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
12 12
13public class RelationalEngineContext implements IEngineContext { 13public class RelationalEngineContext implements IEngineContext {
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 211eacb4..77d86a1c 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
@@ -5,10 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; 8import tools.refinery.viatra.runtime.matchers.context.AbstractQueryMetaContext;
9import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 9import tools.refinery.viatra.runtime.matchers.context.IInputKey;
10import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; 10import tools.refinery.viatra.runtime.matchers.context.InputKeyImplication;
11import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey; 11import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey;
12import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; 12import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
13import tools.refinery.store.query.view.AnySymbolView; 13import tools.refinery.store.query.view.AnySymbolView;
14 14
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 0f2daca8..dadab5dd 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
@@ -5,12 +5,13 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.context; 6package tools.refinery.store.query.viatra.internal.context;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.*; 8import tools.refinery.viatra.runtime.CancellationToken;
9import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 9import tools.refinery.viatra.runtime.matchers.context.*;
10import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 10import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
11import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; 11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 12import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; 13import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
14import tools.refinery.viatra.runtime.matchers.util.Accuracy;
14import tools.refinery.store.model.Model; 15import tools.refinery.store.model.Model;
15import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 16import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
16import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; 17import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper;
@@ -32,10 +33,13 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
32 33
33 private final Model model; 34 private final Model model;
34 35
36 private final CancellationToken cancellationToken;
37
35 RelationalRuntimeContext(ViatraModelQueryAdapterImpl adapter) { 38 RelationalRuntimeContext(ViatraModelQueryAdapterImpl adapter) {
36 model = adapter.getModel(); 39 model = adapter.getModel();
37 metaContext = new RelationalQueryMetaContext(adapter.getStoreAdapter().getInputKeys()); 40 metaContext = new RelationalQueryMetaContext(adapter.getStoreAdapter().getInputKeys());
38 modelUpdateListener = new ModelUpdateListener(adapter); 41 modelUpdateListener = new ModelUpdateListener(adapter);
42 cancellationToken = adapter.getCancellationToken();
39 } 43 }
40 44
41 @Override 45 @Override
@@ -119,10 +123,20 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
119 123
120 private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) { 124 private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) {
121 var relationViewKey = checkKey(key); 125 var relationViewKey = checkKey(key);
122 Iterable<Object[]> allObjects = relationViewKey.getAll(model); 126 Iterable<Object[]> allObjects = getAllObjects(relationViewKey, seedMask, seed);
123 return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); 127 return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
124 } 128 }
125 129
130 private Iterable<Object[]> getAllObjects(AnySymbolView key, TupleMask seedMask, ITuple seed) {
131 for (int i = 0; i < seedMask.indices.length; i++) {
132 int slot = seedMask.indices[i];
133 if (key.canIndexSlot(slot)) {
134 return key.getAdjacent(model, slot, seed.get(i));
135 }
136 }
137 return key.getAll(model);
138 }
139
126 private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { 140 private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) {
127 for (int i = 0; i < seedMask.indices.length; i++) { 141 for (int i = 0; i < seedMask.indices.length; i++) {
128 final Object seedElement = seed.get(i); 142 final Object seedElement = seed.get(i);
@@ -182,4 +196,9 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
182 public void executeAfterTraversal(Runnable runnable) { 196 public void executeAfterTraversal(Runnable runnable) {
183 runnable.run(); 197 runnable.run();
184 } 198 }
199
200 @Override
201 public CancellationToken getCancellationToken() {
202 return cancellationToken;
203 }
185} 204}
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
index cc906f22..ce2a75bd 100644
--- 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
@@ -5,12 +5,12 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.localsearch; 6package tools.refinery.store.query.viatra.internal.localsearch;
7 7
8import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext; 8import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
9import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; 9import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 10import tools.refinery.viatra.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; 11import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
12import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; 12import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
13import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; 13import tools.refinery.viatra.runtime.matchers.util.Accuracy;
14 14
15import java.util.Optional; 15import java.util.Optional;
16 16
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
deleted file mode 100644
index 0c77f587..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java
+++ /dev/null
@@ -1,60 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider;
13import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability;
14import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
15import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
16import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
17import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
18import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
19
20public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory {
21 public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory();
22
23 private RelationalLocalSearchBackendFactory() {
24 }
25
26 @Override
27 public IQueryBackend create(IQueryBackendContext context) {
28 return new LocalSearchBackend(context) {
29 // Create a new {@link IPlanProvider}, because the original {@link LocalSearchBackend#planProvider} is not
30 // accessible.
31 private final IPlanProvider planProvider = new SimplePlanProvider(context.getLogger());
32
33 @Override
34 protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query,
35 QueryEvaluationHint hints) {
36 return new RelationalLocalSearchResultProvider(this, context, query, planProvider, hints);
37 }
38
39 @Override
40 public IQueryBackendFactory getFactory() {
41 return RelationalLocalSearchBackendFactory.this;
42 }
43 };
44 }
45
46 @Override
47 public Class<? extends IQueryBackend> getBackendClass() {
48 return LocalSearchBackend.class;
49 }
50
51 @Override
52 public IMatcherCapability calculateRequiredCapability(PQuery pQuery, QueryEvaluationHint queryEvaluationHint) {
53 return LocalSearchHints.parse(queryEvaluationHint);
54 }
55
56 @Override
57 public boolean isCaching() {
58 return false;
59 }
60}
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
deleted file mode 100644
index da37be14..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java
+++ /dev/null
@@ -1,28 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
9import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
10import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
11import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
12import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler;
13import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
14import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
16
17class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider {
18 public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
19 IPlanProvider planProvider, QueryEvaluationHint userHints) {
20 super(backend, context, query, planProvider, userHints);
21 }
22
23 @Override
24 protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext,
25 LocalSearchHints configuration) {
26 return new RelationalOperationCompiler(runtimeContext);
27 }
28}
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
deleted file mode 100644
index f76ef486..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java
+++ /dev/null
@@ -1,70 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.localsearch;
7
8import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue;
9import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
10import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler;
11import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
12import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext;
13import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
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.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18
19import java.util.*;
20
21public class RelationalOperationCompiler extends GenericOperationCompiler {
22 public RelationalOperationCompiler(IQueryRuntimeContext runtimeContext) {
23 super(runtimeContext);
24 }
25
26 @Override
27 protected void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
28 IInputKey inputKey = typeConstraint.getSupplierKey();
29 Tuple tuple = typeConstraint.getVariablesTuple();
30
31 int[] positions = new int[tuple.getSize()];
32 List<Integer> boundVariableIndices = new ArrayList<>();
33 List<Integer> boundVariables = new ArrayList<>();
34 Set<Integer> unboundVariables = new HashSet<>();
35 for (int i = 0; i < tuple.getSize(); i++) {
36 PVariable variable = (PVariable) tuple.get(i);
37 Integer position = variableMapping.get(variable);
38 positions[i] = position;
39 if (variableBindings.get(typeConstraint).contains(position)) {
40 boundVariableIndices.add(i);
41 boundVariables.add(position);
42 } else {
43 unboundVariables.add(position);
44 }
45 }
46 TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices);
47 TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables);
48 // If multiple tuple elements from the indexer should be bound to the same variable, we must use a
49 // {@link GenericTypeExtend} check whether the tuple elements have the same value.
50 if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) {
51 operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask,
52 unboundVariables.iterator().next()));
53 } else {
54 // Use a fixed version of
55 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles
56 // failed unification of variables correctly.
57 operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables));
58 }
59 }
60
61 @Override
62 protected void createExtend(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) {
63 CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall));
64 // Use a fixed version of
65 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles
66 // failed unification of variables correctly.
67 operations.add(new ExtendPositivePatternCall(information));
68 dependencies.add(information.getCallWithAdornment());
69 }
70}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java
index 99b0a3d8..c4a5a236 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/AbstractViatraMatcher.java
@@ -5,8 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; 8import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
9import org.eclipse.viatra.query.runtime.matchers.backend.IUpdateable; 9import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
10import tools.refinery.store.query.dnf.Query; 10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.resultset.AbstractResultSet; 11import tools.refinery.store.query.resultset.AbstractResultSet;
12import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 12import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
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
index 47efb2aa..44038669 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; 8import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
11 11
@@ -13,7 +13,7 @@ import java.util.Iterator;
13 13
14class FunctionalCursor<T> implements Cursor<Tuple, T> { 14class FunctionalCursor<T> implements Cursor<Tuple, T> {
15 private final IterableIndexer indexer; 15 private final IterableIndexer indexer;
16 private final Iterator<org.eclipse.viatra.query.runtime.matchers.tuple.Tuple> iterator; 16 private final Iterator<tools.refinery.viatra.runtime.matchers.tuple.Tuple> iterator;
17 private boolean terminated; 17 private boolean terminated;
18 private Tuple key; 18 private Tuple key;
19 private T value; 19 private T value;
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
index db4740cd..fb9e4df6 100644
--- 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
@@ -5,10 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; 8import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 9import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; 10import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; 11import tools.refinery.viatra.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor; 12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.query.dnf.FunctionalQuery; 13import tools.refinery.store.query.dnf.FunctionalQuery;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
@@ -17,9 +17,9 @@ import tools.refinery.store.tuple.Tuple;
17/** 17/**
18 * Directly access the tuples inside a VIATRA pattern matcher.<p> 18 * Directly access the tuples inside a VIATRA pattern matcher.<p>
19 * This class neglects calling 19 * This class neglects calling
20 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} 20 * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
21 * and 21 * and
22 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, 22 * {@link tools.refinery.viatra.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 23 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
24 * implementation for these methods. 24 * implementation for these methods.
25 * Using this class with any other runtime context may lead to undefined behavior. 25 * Using this class with any other runtime context may lead to undefined behavior.
@@ -37,7 +37,7 @@ public class FunctionalViatraMatcher<T> extends AbstractViatraMatcher<T> {
37 emptyMask = TupleMask.empty(arityWithOutput); 37 emptyMask = TupleMask.empty(arityWithOutput);
38 omitOutputMask = TupleMask.omit(arity, arityWithOutput); 38 omitOutputMask = TupleMask.omit(arity, arityWithOutput);
39 if (backend instanceof RetePatternMatcher reteBackend) { 39 if (backend instanceof RetePatternMatcher reteBackend) {
40 var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask); 40 var maybeIterableOmitOutputIndexer = reteBackend.getInternalIndexer(omitOutputMask);
41 if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) { 41 if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) {
42 omitOutputIndexer = iterableOmitOutputIndexer; 42 omitOutputIndexer = iterableOmitOutputIndexer;
43 } else { 43 } else {
@@ -76,7 +76,7 @@ public class FunctionalViatraMatcher<T> extends AbstractViatraMatcher<T> {
76 } 76 }
77 77
78 @Override 78 @Override
79 public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { 79 public void update(tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) {
80 var key = MatcherUtils.keyToRefineryTuple(updateElement); 80 var key = MatcherUtils.keyToRefineryTuple(updateElement);
81 var value = MatcherUtils.<T>getValue(updateElement); 81 var value = MatcherUtils.<T>getValue(updateElement);
82 if (isInsertion) { 82 if (isInsertion) {
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
deleted file mode 100644
index 15f00b2d..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java
+++ /dev/null
@@ -1,53 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra.internal.matcher;
7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.rete.index.Indexer;
10import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
12import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo;
13
14import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles;
16import java.lang.invoke.MethodType;
17
18final class IndexerUtils {
19 private static final MethodHandle GET_ENGINE_HANDLE;
20 private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE;
21 private static final MethodHandle ACCESS_PROJECTION_HANDLE;
22
23 static {
24 try {
25 var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup());
26 GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class);
27 GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace",
28 RecipeTraceInfo.class);
29 ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection",
30 MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class));
31 } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) {
32 throw new IllegalStateException("Cannot access private members of %s"
33 .formatted(RetePatternMatcher.class.getPackageName()), e);
34 }
35 }
36
37 private IndexerUtils() {
38 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
39 }
40
41 public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) {
42 try {
43 var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend);
44 var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend);
45 return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask);
46 } catch (Error e) {
47 // Fatal JVM errors should not be wrapped.
48 throw e;
49 } catch (Throwable e) {
50 throw new IllegalStateException("Cannot access matcher for mask " + mask, e);
51 }
52 }
53}
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
index 6e24812a..6cda23cd 100644
--- 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
@@ -5,8 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 8import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 9import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
10import org.jetbrains.annotations.Nullable; 10import org.jetbrains.annotations.Nullable;
11import tools.refinery.store.tuple.*; 11import tools.refinery.store.tuple.*;
12 12
@@ -17,7 +17,7 @@ final class MatcherUtils {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 } 18 }
19 19
20 public static org.eclipse.viatra.query.runtime.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) { 20 public static tools.refinery.viatra.runtime.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) {
21 if (refineryTuple instanceof Tuple0) { 21 if (refineryTuple instanceof Tuple0) {
22 return Tuples.staticArityFlatTupleOf(); 22 return Tuples.staticArityFlatTupleOf();
23 } else if (refineryTuple instanceof Tuple1) { 23 } else if (refineryTuple instanceof Tuple1) {
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
index 5b82c4b7..c0be70ba 100644
--- 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
@@ -5,9 +5,9 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; 8import tools.refinery.viatra.runtime.api.GenericPatternMatcher;
9import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; 9import tools.refinery.viatra.runtime.api.GenericQuerySpecification;
10import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; 10import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
11 11
12public class RawPatternMatcher extends GenericPatternMatcher { 12public class RawPatternMatcher extends GenericPatternMatcher {
13 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) { 13 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
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
index 1dc8f5db..53475218 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 8import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
11 11
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
index ac95dcc0..da75e864 100644
--- 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
@@ -5,10 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; 8import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
9import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 9import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
10import org.eclipse.viatra.query.runtime.rete.index.Indexer; 10import tools.refinery.viatra.runtime.rete.index.Indexer;
11import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; 11import tools.refinery.viatra.runtime.rete.matcher.RetePatternMatcher;
12import tools.refinery.store.map.Cursor; 12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.map.Cursors; 13import tools.refinery.store.map.Cursors;
14import tools.refinery.store.query.dnf.RelationalQuery; 14import tools.refinery.store.query.dnf.RelationalQuery;
@@ -18,9 +18,9 @@ import tools.refinery.store.tuple.Tuple;
18/** 18/**
19 * Directly access the tuples inside a VIATRA pattern matcher.<p> 19 * Directly access the tuples inside a VIATRA pattern matcher.<p>
20 * This class neglects calling 20 * This class neglects calling
21 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} 21 * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
22 * and 22 * and
23 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, 23 * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)},
24 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial 24 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
25 * implementation for these methods. 25 * implementation for these methods.
26 * Using this class with any other runtime context may lead to undefined behavior. 26 * Using this class with any other runtime context may lead to undefined behavior.
@@ -37,7 +37,7 @@ public class RelationalViatraMatcher extends AbstractViatraMatcher<Boolean> {
37 emptyMask = TupleMask.empty(arity); 37 emptyMask = TupleMask.empty(arity);
38 identityMask = TupleMask.identity(arity); 38 identityMask = TupleMask.identity(arity);
39 if (backend instanceof RetePatternMatcher reteBackend) { 39 if (backend instanceof RetePatternMatcher reteBackend) {
40 emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask); 40 emptyMaskIndexer = reteBackend.getInternalIndexer(emptyMask);
41 } else { 41 } else {
42 emptyMaskIndexer = null; 42 emptyMaskIndexer = null;
43 } 43 }
@@ -73,7 +73,7 @@ public class RelationalViatraMatcher extends AbstractViatraMatcher<Boolean> {
73 } 73 }
74 74
75 @Override 75 @Override
76 public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { 76 public void update(tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) {
77 var key = MatcherUtils.toRefineryTuple(updateElement); 77 var key = MatcherUtils.toRefineryTuple(updateElement);
78 notifyChange(key, !isInsertion, isInsertion); 78 notifyChange(key, !isInsertion, isInsertion);
79 } 79 }
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
index b0b507fe..093ade96 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 8import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
11 11
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/CheckEvaluator.java
index cf127291..ada154d4 100644
--- 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/CheckEvaluator.java
@@ -5,11 +5,11 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; 8import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.Term; 9import tools.refinery.store.query.term.Term;
10 10
11class AssumptionEvaluator extends TermEvaluator<Boolean> { 11class CheckEvaluator extends TermEvaluator<Boolean> {
12 public AssumptionEvaluator(Term<Boolean> term) { 12 public CheckEvaluator(Term<Boolean> term) {
13 super(term); 13 super(term);
14 } 14 }
15 15
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
index 5b0ea61d..492bd054 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
@@ -5,24 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 8import tools.refinery.store.query.Constraint;
9import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
10import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
11import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
12import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
13import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
14import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
16import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*;
17import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
18import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
19import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
20import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
21import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
22import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection;
23import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
24import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
25import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
26import tools.refinery.store.query.dnf.Dnf; 9import tools.refinery.store.query.dnf.Dnf;
27import tools.refinery.store.query.dnf.DnfClause; 10import tools.refinery.store.query.dnf.DnfClause;
28import tools.refinery.store.query.dnf.SymbolicParameter; 11import tools.refinery.store.query.dnf.SymbolicParameter;
@@ -33,17 +16,33 @@ import tools.refinery.store.query.term.StatelessAggregator;
33import tools.refinery.store.query.term.Variable; 16import tools.refinery.store.query.term.Variable;
34import tools.refinery.store.query.view.AnySymbolView; 17import tools.refinery.store.query.view.AnySymbolView;
35import tools.refinery.store.util.CycleDetectingMapper; 18import tools.refinery.store.util.CycleDetectingMapper;
36 19import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
37import java.util.*; 20import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
21import tools.refinery.viatra.runtime.matchers.context.IInputKey;
22import tools.refinery.viatra.runtime.matchers.psystem.PBody;
23import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
24import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
25import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
26import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
27import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*;
28import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*;
29import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.Connectivity;
30import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
31import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection;
32import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
33import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
34import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
35
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
38import java.util.function.Function; 40import java.util.function.Function;
39import java.util.stream.Collectors;
40 41
41public class Dnf2PQuery { 42public class Dnf2PQuery {
42 private static final Object P_CONSTRAINT_LOCK = new Object();
43 private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name, 43 private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name,
44 this::doTranslate); 44 this::doTranslate);
45 private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this); 45 private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this);
46 private final Map<Dnf, QueryEvaluationHint> hintOverrides = new LinkedHashMap<>();
47 private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null, 46 private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
48 (IQueryBackendFactory) null); 47 (IQueryBackendFactory) null);
49 48
@@ -59,36 +58,15 @@ public class Dnf2PQuery {
59 return wrapperFactory.getSymbolViews(); 58 return wrapperFactory.getSymbolViews();
60 } 59 }
61 60
62 public void hint(Dnf dnf, QueryEvaluationHint hint) {
63 hintOverrides.compute(dnf, (ignoredKey, existingHint) ->
64 existingHint == null ? hint : existingHint.overrideBy(hint));
65 }
66
67 private QueryEvaluationHint consumeHint(Dnf dnf) {
68 var defaultHint = computeHint.apply(dnf);
69 var existingHint = hintOverrides.remove(dnf);
70 return defaultHint.overrideBy(existingHint);
71 }
72
73 public void assertNoUnusedHints() {
74 if (hintOverrides.isEmpty()) {
75 return;
76 }
77 var unusedHints = hintOverrides.keySet().stream().map(Dnf::name).collect(Collectors.joining(", "));
78 throw new IllegalStateException(
79 "Unused query evaluation hints for %s. Hints must be set before a query is added to the engine"
80 .formatted(unusedHints));
81 }
82
83 private RawPQuery doTranslate(Dnf dnfQuery) { 61 private RawPQuery doTranslate(Dnf dnfQuery) {
84 var pQuery = new RawPQuery(dnfQuery.getUniqueName()); 62 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
85 pQuery.setEvaluationHints(consumeHint(dnfQuery)); 63 pQuery.setEvaluationHints(computeHint.apply(dnfQuery));
86 64
87 Map<SymbolicParameter, PParameter> parameters = new HashMap<>(); 65 Map<SymbolicParameter, PParameter> parameters = new HashMap<>();
88 List<PParameter> parameterList = new ArrayList<>(); 66 List<PParameter> parameterList = new ArrayList<>();
89 for (var parameter : dnfQuery.getSymbolicParameters()) { 67 for (var parameter : dnfQuery.getSymbolicParameters()) {
90 var direction = switch (parameter.getDirection()) { 68 var direction = switch (parameter.getDirection()) {
91 case OUT -> parameter.isUnifiable() ? PParameterDirection.INOUT : PParameterDirection.OUT; 69 case OUT -> PParameterDirection.INOUT;
92 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported" 70 case IN -> throw new IllegalArgumentException("Query %s with input parameter %s is not supported"
93 .formatted(dnfQuery, parameter.getVariable())); 71 .formatted(dnfQuery, parameter.getVariable()));
94 }; 72 };
@@ -110,22 +88,17 @@ public class Dnf2PQuery {
110 pQuery.addAnnotation(functionalDependencyAnnotation); 88 pQuery.addAnnotation(functionalDependencyAnnotation);
111 } 89 }
112 90
113 // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates 91 for (DnfClause clause : dnfQuery.getClauses()) {
114 // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating 92 PBody body = new PBody(pQuery);
115 // any query literals to avoid a data race. 93 List<ExportedParameter> parameterExports = new ArrayList<>();
116 synchronized (P_CONSTRAINT_LOCK) { 94 for (var parameter : dnfQuery.getSymbolicParameters()) {
117 for (DnfClause clause : dnfQuery.getClauses()) { 95 PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName());
118 PBody body = new PBody(pQuery); 96 parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter)));
119 List<ExportedParameter> parameterExports = new ArrayList<>(); 97 }
120 for (var parameter : dnfQuery.getSymbolicParameters()) { 98 body.setSymbolicParameters(parameterExports);
121 PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName()); 99 pQuery.addBody(body);
122 parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter))); 100 for (Literal literal : clause.literals()) {
123 } 101 translateLiteral(literal, body);
124 body.setSymbolicParameters(parameterExports);
125 pQuery.addBody(body);
126 for (Literal literal : clause.literals()) {
127 translateLiteral(literal, body);
128 }
129 } 102 }
130 } 103 }
131 104
@@ -141,21 +114,23 @@ public class Dnf2PQuery {
141 translateConstantLiteral(constantLiteral, body); 114 translateConstantLiteral(constantLiteral, body);
142 } else if (literal instanceof AssignLiteral<?> assignLiteral) { 115 } else if (literal instanceof AssignLiteral<?> assignLiteral) {
143 translateAssignLiteral(assignLiteral, body); 116 translateAssignLiteral(assignLiteral, body);
144 } else if (literal instanceof AssumeLiteral assumeLiteral) { 117 } else if (literal instanceof CheckLiteral checkLiteral) {
145 translateAssumeLiteral(assumeLiteral, body); 118 translateCheckLiteral(checkLiteral, body);
146 } else if (literal instanceof CountLiteral countLiteral) { 119 } else if (literal instanceof CountLiteral countLiteral) {
147 translateCountLiteral(countLiteral, body); 120 translateCountLiteral(countLiteral, body);
148 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) { 121 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) {
149 translateAggregationLiteral(aggregationLiteral, body); 122 translateAggregationLiteral(aggregationLiteral, body);
123 } else if (literal instanceof RepresentativeElectionLiteral representativeElectionLiteral) {
124 translateRepresentativeElectionLiteral(representativeElectionLiteral, body);
150 } else { 125 } else {
151 throw new IllegalArgumentException("Unknown literal: " + literal.toString()); 126 throw new IllegalArgumentException("Unknown literal: " + literal.toString());
152 } 127 }
153 } 128 }
154 129
155 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) { 130 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
156 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.left().getUniqueName()); 131 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.getLeft().getUniqueName());
157 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.right().getUniqueName()); 132 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.getRight().getUniqueName());
158 if (equivalenceLiteral.positive()) { 133 if (equivalenceLiteral.isPositive()) {
159 new Equality(body, varSource, varTarget); 134 new Equality(body, varSource, varTarget);
160 } else { 135 } else {
161 new Inequality(body, varSource, varTarget); 136 new Inequality(body, varSource, varTarget);
@@ -180,15 +155,7 @@ public class Dnf2PQuery {
180 } 155 }
181 case TRANSITIVE -> { 156 case TRANSITIVE -> {
182 var substitution = translateSubstitution(callLiteral.getArguments(), body); 157 var substitution = translateSubstitution(callLiteral.getArguments(), body);
183 var constraint = callLiteral.getTarget(); 158 var pattern = wrapConstraintWithIdentityArguments(callLiteral.getTarget());
184 PQuery pattern;
185 if (constraint instanceof Dnf dnf) {
186 pattern = translate(dnf);
187 } else if (constraint instanceof AnySymbolView symbolView) {
188 pattern = wrapperFactory.wrapSymbolViewIdentityArguments(symbolView);
189 } else {
190 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
191 }
192 new BinaryTransitiveClosure(body, substitution, pattern); 159 new BinaryTransitiveClosure(body, substitution, pattern);
193 } 160 }
194 case NEGATIVE -> { 161 case NEGATIVE -> {
@@ -201,6 +168,16 @@ public class Dnf2PQuery {
201 } 168 }
202 } 169 }
203 170
171 private PQuery wrapConstraintWithIdentityArguments(Constraint constraint) {
172 if (constraint instanceof Dnf dnf) {
173 return translate(dnf);
174 } else if (constraint instanceof AnySymbolView symbolView) {
175 return wrapperFactory.wrapSymbolViewIdentityArguments(symbolView);
176 } else {
177 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
178 }
179 }
180
204 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) { 181 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
205 int arity = substitution.size(); 182 int arity = substitution.size();
206 Object[] variables = new Object[arity]; 183 Object[] variables = new Object[arity];
@@ -212,13 +189,13 @@ public class Dnf2PQuery {
212 } 189 }
213 190
214 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) { 191 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) {
215 var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName()); 192 var variable = body.getOrCreateVariableByName(constantLiteral.getVariable().getUniqueName());
216 new ConstantValue(body, variable, constantLiteral.nodeId()); 193 new ConstantValue(body, variable, tools.refinery.store.tuple.Tuple.of(constantLiteral.getNodeId()));
217 } 194 }
218 195
219 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) { 196 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) {
220 var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName()); 197 var variable = body.getOrCreateVariableByName(assignLiteral.getVariable().getUniqueName());
221 var term = assignLiteral.term(); 198 var term = assignLiteral.getTerm();
222 if (term instanceof ConstantTerm<T> constantTerm) { 199 if (term instanceof ConstantTerm<T> constantTerm) {
223 new ConstantValue(body, variable, constantTerm.getValue()); 200 new ConstantValue(body, variable, constantTerm.getValue());
224 } else { 201 } else {
@@ -227,8 +204,8 @@ public class Dnf2PQuery {
227 } 204 }
228 } 205 }
229 206
230 private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) { 207 private void translateCheckLiteral(CheckLiteral checkLiteral, PBody body) {
231 var evaluator = new AssumptionEvaluator(assumeLiteral.term()); 208 var evaluator = new CheckEvaluator(checkLiteral.getTerm());
232 new ExpressionEvaluation(body, evaluator, null); 209 new ExpressionEvaluation(body, evaluator, null);
233 } 210 }
234 211
@@ -263,4 +240,14 @@ public class Dnf2PQuery {
263 new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable, 240 new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable,
264 aggregatedColumn); 241 aggregatedColumn);
265 } 242 }
243
244 private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) {
245 var substitution = translateSubstitution(literal.getArguments(), body);
246 var pattern = wrapConstraintWithIdentityArguments(literal.getTarget());
247 var connectivity = switch (literal.getConnectivity()) {
248 case WEAK -> Connectivity.WEAK;
249 case STRONG -> Connectivity.STRONG;
250 };
251 new RepresentativeElectionConstraint(body, substitution, pattern, connectivity);
252 }
266} 253}
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
index 2b7280f2..d21131e5 100644
--- 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
@@ -5,17 +5,17 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import tools.refinery.viatra.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; 9import tools.refinery.viatra.runtime.matchers.psystem.PBody;
10import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; 10import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; 11import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; 12import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; 13import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; 14import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; 15import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; 16import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19import tools.refinery.store.query.Constraint; 19import tools.refinery.store.query.Constraint;
20import tools.refinery.store.query.dnf.Dnf; 20import tools.refinery.store.query.dnf.Dnf;
21import tools.refinery.store.query.dnf.DnfUtils; 21import tools.refinery.store.query.dnf.DnfUtils;
@@ -33,7 +33,7 @@ class QueryWrapperFactory {
33 private final Dnf2PQuery dnf2PQuery; 33 private final Dnf2PQuery dnf2PQuery;
34 private final Map<AnySymbolView, SymbolViewWrapper> view2WrapperMap = new LinkedHashMap<>(); 34 private final Map<AnySymbolView, SymbolViewWrapper> view2WrapperMap = new LinkedHashMap<>();
35 private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>( 35 private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>(
36 RemappedConstraint::toString, this::doWrapConstraint); 36 this::doWrapConstraint);
37 37
38 QueryWrapperFactory(Dnf2PQuery dnf2PQuery) { 38 QueryWrapperFactory(Dnf2PQuery dnf2PQuery) {
39 this.dnf2PQuery = dnf2PQuery; 39 this.dnf2PQuery = dnf2PQuery;
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 255738c5..06644bf2 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
@@ -5,14 +5,14 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; 8import tools.refinery.viatra.runtime.api.GenericQuerySpecification;
9import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; 9import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
10import org.eclipse.viatra.query.runtime.api.scope.QueryScope; 10import tools.refinery.viatra.runtime.api.scope.QueryScope;
11import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; 11import tools.refinery.viatra.runtime.matchers.psystem.PBody;
12import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; 12import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
13import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; 13import tools.refinery.viatra.runtime.matchers.psystem.queries.BasePQuery;
14import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; 14import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
15import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; 15import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility;
16import tools.refinery.store.query.viatra.internal.RelationalScope; 16import tools.refinery.store.query.viatra.internal.RelationalScope;
17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; 17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
18 18
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
index 461416f7..ba99cf9a 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; 8import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatefulAggregate; 9import tools.refinery.store.query.term.StatefulAggregate;
10import tools.refinery.store.query.term.StatefulAggregator; 10import tools.refinery.store.query.term.StatefulAggregator;
11 11
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
index 49175d75..bf2c2f4f 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; 8import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
9import tools.refinery.store.query.term.StatelessAggregator; 9import tools.refinery.store.query.term.StatelessAggregator;
10 10
11import java.util.stream.Stream; 11import java.util.stream.Stream;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java
index a777613e..a774404e 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; 8import tools.refinery.viatra.runtime.matchers.context.common.BaseInputKeyWrapper;
9import tools.refinery.store.query.view.AnySymbolView; 9import tools.refinery.store.query.view.AnySymbolView;
10 10
11public class SymbolViewWrapper extends BaseInputKeyWrapper<AnySymbolView> { 11public class SymbolViewWrapper extends BaseInputKeyWrapper<AnySymbolView> {
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
index 1187f57a..5df861a6 100644
--- 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
@@ -5,8 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator; 8import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
9import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; 9import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
10import tools.refinery.store.query.term.Term; 10import tools.refinery.store.query.term.Term;
11import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
12 12
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
index 62cb8b3a..b9ae8ab2 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.pquery; 6package tools.refinery.store.query.viatra.internal.pquery;
7 7
8import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; 8import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
9import tools.refinery.store.query.term.DataVariable; 9import tools.refinery.store.query.term.DataVariable;
10import tools.refinery.store.query.valuation.Valuation; 10import tools.refinery.store.query.valuation.Valuation;
11 11
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 986bb0b1..e1bc9efc 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
@@ -5,9 +5,9 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import tools.refinery.viatra.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 9import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener;
10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 10import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 11import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
12import tools.refinery.store.query.view.AnySymbolView; 12import tools.refinery.store.query.view.AnySymbolView;
13import tools.refinery.store.query.view.SymbolView; 13import tools.refinery.store.query.view.SymbolView;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
index efdbfcbe..73c4a3f9 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewFilter.java
@@ -5,10 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import tools.refinery.viatra.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 9import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener;
10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 10import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
11import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12 12
13import java.util.Arrays; 13import java.util.Arrays;
14import java.util.Objects; 14import java.util.Objects;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java
index f1a2ac7c..d0cdda72 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/SymbolViewUpdateListener.java
@@ -5,10 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
7 7
8import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 8import tools.refinery.viatra.runtime.matchers.context.IInputKey;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 9import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener;
10import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 10import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
11import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.store.model.Interpretation; 12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.model.InterpretationListener; 13import tools.refinery.store.model.InterpretationListener;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java
index 45d35571..9dc739f1 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingViewUpdateListener.java
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 8import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation; 9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.view.SymbolView; 11import tools.refinery.store.query.view.SymbolView;
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java
index c18dbafb..7dbd50b3 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.update; 6package tools.refinery.store.query.viatra.internal.update;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 8import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
9import tools.refinery.store.model.Interpretation; 9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 10import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
11import tools.refinery.store.query.view.TuplePreservingView; 11import tools.refinery.store.query.view.TuplePreservingView;
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
index 6aae2ebe..85bdc204 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.model.ModelStore; 9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter; 10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
@@ -98,6 +98,7 @@ class DiagonalQueryTest {
98 .build(); 98 .build();
99 99
100 var model = store.createEmptyModel(); 100 var model = store.createEmptyModel();
101
101 var personInterpretation = model.getInterpretation(person); 102 var personInterpretation = model.getInterpretation(person);
102 var symbolInterpretation = model.getInterpretation(symbol); 103 var symbolInterpretation = model.getInterpretation(symbol);
103 var queryEngine = model.getAdapter(ModelQueryAdapter.class); 104 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
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
index 258127e7..7190d8f1 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import tools.refinery.store.map.Cursor; 9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter; 11import tools.refinery.store.query.ModelQueryAdapter;
@@ -29,7 +29,7 @@ import static org.hamcrest.Matchers.is;
29import static org.hamcrest.Matchers.nullValue; 29import static org.hamcrest.Matchers.nullValue;
30import static org.junit.jupiter.api.Assertions.assertAll; 30import static org.junit.jupiter.api.Assertions.assertAll;
31import static org.junit.jupiter.api.Assertions.assertThrows; 31import static org.junit.jupiter.api.Assertions.assertThrows;
32import static tools.refinery.store.query.literal.Literals.assume; 32import static tools.refinery.store.query.literal.Literals.check;
33import static tools.refinery.store.query.term.int_.IntTerms.*; 33import static tools.refinery.store.query.term.int_.IntTerms.*;
34import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; 34import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
35import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; 35import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
@@ -390,7 +390,7 @@ class FunctionalQueryTest {
390 var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( 390 var query = Query.of("InvalidAssume", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of(
391 personView.call(p1), 391 personView.call(p1),
392 ageView.call(p1, x), 392 ageView.call(p1, x),
393 assume(lessEq(div(constant(120), x), constant(5))) 393 check(lessEq(div(constant(120), x), constant(5)))
394 ))); 394 )));
395 395
396 var store = ModelStore.builder() 396 var store = ModelStore.builder()
@@ -424,6 +424,42 @@ class FunctionalQueryTest {
424 } 424 }
425 425
426 @QueryEngineTest 426 @QueryEngineTest
427 void multipleAssignmentTest(QueryEvaluationHint hint) {
428 var query = Query.of("MultipleAssignment", Integer.class, (builder, p1, p2, output) -> builder
429 .clause(Integer.class, Integer.class, (x1, x2) -> List.of(
430 ageView.call(p1, x1),
431 ageView.call(p2, x2),
432 output.assign(mul(x1, constant(2))),
433 output.assign(mul(x2, constant(3)))
434 )));
435
436 var store = ModelStore.builder()
437 .symbols(age)
438 .with(ViatraModelQueryAdapter.builder()
439 .defaultHint(hint)
440 .queries(query))
441 .build();
442
443 var model = store.createEmptyModel();
444 var ageInterpretation = model.getInterpretation(age);
445 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
446 var queryResultSet = queryEngine.getResultSet(query);
447
448 ageInterpretation.put(Tuple.of(0), 3);
449 ageInterpretation.put(Tuple.of(1), 2);
450 ageInterpretation.put(Tuple.of(2), 15);
451 ageInterpretation.put(Tuple.of(3), 10);
452
453 queryEngine.flushChanges();
454 assertNullableResults(Map.of(
455 Tuple.of(0, 1), Optional.of(6),
456 Tuple.of(1, 0), Optional.empty(),
457 Tuple.of(2, 3), Optional.of(30),
458 Tuple.of(3, 2), Optional.empty()
459 ), queryResultSet);
460 }
461
462 @QueryEngineTest
427 void notFunctionalTest(QueryEvaluationHint hint) { 463 void notFunctionalTest(QueryEvaluationHint hint) {
428 var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of( 464 var query = Query.of("NotFunctional", Integer.class, (builder, p1, output) -> builder.clause((p2) -> List.of(
429 personView.call(p1), 465 personView.call(p1),
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 25bcb0dc..ce403e3a 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
@@ -5,11 +5,13 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter; 11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.Query; 13import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable; 15import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.tests.QueryEngineTest; 16import tools.refinery.store.query.viatra.tests.QueryEngineTest;
15import tools.refinery.store.query.view.AnySymbolView; 17import tools.refinery.store.query.view.AnySymbolView;
@@ -23,7 +25,7 @@ import tools.refinery.store.tuple.Tuple;
23import java.util.List; 25import java.util.List;
24import java.util.Map; 26import java.util.Map;
25 27
26import static tools.refinery.store.query.literal.Literals.assume; 28import static tools.refinery.store.query.literal.Literals.check;
27import static tools.refinery.store.query.literal.Literals.not; 29import static tools.refinery.store.query.literal.Literals.not;
28import static tools.refinery.store.query.term.int_.IntTerms.constant; 30import static tools.refinery.store.query.term.int_.IntTerms.constant;
29import static tools.refinery.store.query.term.int_.IntTerms.greaterEq; 31import static tools.refinery.store.query.term.int_.IntTerms.greaterEq;
@@ -108,6 +110,44 @@ class QueryTest {
108 } 110 }
109 111
110 @QueryEngineTest 112 @QueryEngineTest
113 void isConstantTest(QueryEvaluationHint hint) {
114 var predicate = Query.of("RelationConstraint", (builder, p1, p2) -> builder.clause(
115 personView.call(p1),
116 p1.isConstant(1),
117 friendMustView.call(p1, p2)
118 ));
119
120 var store = ModelStore.builder()
121 .symbols(person, friend)
122 .with(ViatraModelQueryAdapter.builder()
123 .defaultHint(hint)
124 .queries(predicate))
125 .build();
126
127 var model = store.createEmptyModel();
128 var personInterpretation = model.getInterpretation(person);
129 var friendInterpretation = model.getInterpretation(friend);
130 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
131 var predicateResultSet = queryEngine.getResultSet(predicate);
132
133 personInterpretation.put(Tuple.of(0), true);
134 personInterpretation.put(Tuple.of(1), true);
135 personInterpretation.put(Tuple.of(2), true);
136
137 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
138 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
139 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
140
141 queryEngine.flushChanges();
142 assertResults(Map.of(
143 Tuple.of(0, 1), false,
144 Tuple.of(1, 0), true,
145 Tuple.of(1, 2), true,
146 Tuple.of(2, 1), false
147 ), predicateResultSet);
148 }
149
150 @QueryEngineTest
111 void existTest(QueryEvaluationHint hint) { 151 void existTest(QueryEvaluationHint hint) {
112 var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of( 152 var predicate = Query.of("Exists", (builder, p1) -> builder.clause((p2) -> List.of(
113 personView.call(p1), 153 personView.call(p1),
@@ -312,6 +352,53 @@ class QueryTest {
312 } 352 }
313 353
314 @QueryEngineTest 354 @QueryEngineTest
355 void patternCallInputArgumentTest(QueryEvaluationHint hint) {
356 var friendPredicate = Dnf.of("Friend", builder -> {
357 var p1 = builder.parameter("p1", ParameterDirection.IN);
358 var p2 = builder.parameter("p2", ParameterDirection.IN);
359 builder.clause(
360 personView.call(p1),
361 personView.call(p2),
362 friendMustView.call(p1, p2)
363 );
364 });
365 var predicate = Query.of("PositivePatternCall", (builder, p3, p4) -> builder.clause(
366 personView.call(p3),
367 personView.call(p4),
368 friendPredicate.call(p3, p4)
369 ));
370
371 var store = ModelStore.builder()
372 .symbols(person, friend)
373 .with(ViatraModelQueryAdapter.builder()
374 .defaultHint(hint)
375 .queries(predicate))
376 .build();
377
378 var model = store.createEmptyModel();
379 var personInterpretation = model.getInterpretation(person);
380 var friendInterpretation = model.getInterpretation(friend);
381 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
382 var predicateResultSet = queryEngine.getResultSet(predicate);
383
384 personInterpretation.put(Tuple.of(0), true);
385 personInterpretation.put(Tuple.of(1), true);
386 personInterpretation.put(Tuple.of(2), true);
387
388 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
389 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
390 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
391
392 queryEngine.flushChanges();
393 assertResults(Map.of(
394 Tuple.of(0, 1), true,
395 Tuple.of(1, 0), true,
396 Tuple.of(1, 2), true,
397 Tuple.of(2, 1), false
398 ), predicateResultSet);
399 }
400
401 @QueryEngineTest
315 void negativeRelationViewTest(QueryEvaluationHint hint) { 402 void negativeRelationViewTest(QueryEvaluationHint hint) {
316 var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause( 403 var predicate = Query.of("NegativePatternCall", (builder, p1, p2) -> builder.clause(
317 personView.call(p1), 404 personView.call(p1),
@@ -652,7 +739,7 @@ class QueryTest {
652 var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of( 739 var query = Query.of("Constraint", (builder, p1) -> builder.clause(Integer.class, (x) -> List.of(
653 personView.call(p1), 740 personView.call(p1),
654 ageView.call(p1, x), 741 ageView.call(p1, x),
655 assume(greaterEq(x, constant(18))) 742 check(greaterEq(x, constant(18)))
656 ))); 743 )));
657 744
658 var store = ModelStore.builder() 745 var store = ModelStore.builder()
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 32f51cf0..3f8c060a 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra; 6package tools.refinery.store.query.viatra;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter; 11import tools.refinery.store.query.ModelQueryAdapter;
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/StronglyConnectedComponentsTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/StronglyConnectedComponentsTest.java
new file mode 100644
index 00000000..37795ff3
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/StronglyConnectedComponentsTest.java
@@ -0,0 +1,261 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.literal.Connectivity;
13import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20import java.util.Map;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
25
26class StronglyConnectedComponentsTest {
27 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
28 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
29
30 @Test
31 void symbolViewTest() {
32 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
33 .clause(v1 -> List.of(
34 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1),
35 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1)
36 )));
37
38 var store = ModelStore.builder()
39 .symbols(friend)
40 .with(ViatraModelQueryAdapter.builder()
41 .queries(query))
42 .build();
43
44 var model = store.createEmptyModel();
45 var friendInterpretation = model.getInterpretation(friend);
46 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
47 var resultSet = queryEngine.getResultSet(query);
48
49 friendInterpretation.put(Tuple.of(0, 1), true);
50 friendInterpretation.put(Tuple.of(1, 0), true);
51 friendInterpretation.put(Tuple.of(1, 2), true);
52 queryEngine.flushChanges();
53
54 assertResults(Map.of(
55 Tuple.of(0, 0), true,
56 Tuple.of(0, 1), true,
57 Tuple.of(1, 0), true,
58 Tuple.of(1, 1), true,
59 Tuple.of(2, 2), true
60 ), resultSet);
61 }
62
63 @Test
64 void symbolViewInsertTest() {
65 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
66 .clause(v1 -> List.of(
67 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1),
68 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1)
69 )));
70
71 var store = ModelStore.builder()
72 .symbols(friend)
73 .with(ViatraModelQueryAdapter.builder()
74 .queries(query))
75 .build();
76
77 var model = store.createEmptyModel();
78 var friendInterpretation = model.getInterpretation(friend);
79 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
80 var resultSet = queryEngine.getResultSet(query);
81
82 friendInterpretation.put(Tuple.of(0, 1), true);
83 friendInterpretation.put(Tuple.of(1, 0), true);
84 friendInterpretation.put(Tuple.of(1, 2), true);
85 queryEngine.flushChanges();
86
87 friendInterpretation.put(Tuple.of(2, 0), true);
88 friendInterpretation.put(Tuple.of(0, 3), true);
89 queryEngine.flushChanges();
90
91 assertResults(Map.of(
92 Tuple.of(0, 0), true,
93 Tuple.of(0, 1), true,
94 Tuple.of(0, 2), true,
95 Tuple.of(1, 1), true,
96 Tuple.of(1, 0), true,
97 Tuple.of(1, 2), true,
98 Tuple.of(2, 0), true,
99 Tuple.of(2, 1), true,
100 Tuple.of(2, 2), true,
101 Tuple.of(3, 3), true
102 ), resultSet);
103 }
104
105 @Test
106 void symbolViewDeleteTest() {
107 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
108 .clause(v1 -> List.of(
109 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1),
110 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1)
111 )));
112
113 var store = ModelStore.builder()
114 .symbols(friend)
115 .with(ViatraModelQueryAdapter.builder()
116 .queries(query))
117 .build();
118
119 var model = store.createEmptyModel();
120 var friendInterpretation = model.getInterpretation(friend);
121 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
122 var resultSet = queryEngine.getResultSet(query);
123
124 friendInterpretation.put(Tuple.of(0, 1), true);
125 friendInterpretation.put(Tuple.of(1, 0), true);
126 friendInterpretation.put(Tuple.of(1, 2), true);
127 queryEngine.flushChanges();
128
129 friendInterpretation.put(Tuple.of(1, 0), false);
130 friendInterpretation.put(Tuple.of(1, 2), false);
131 queryEngine.flushChanges();
132
133 assertResults(Map.of(
134 Tuple.of(0, 0), true,
135 Tuple.of(1, 1), true
136 ), resultSet);
137 }
138
139 @Test
140 void diagonalSymbolViewTest() {
141 var person = Symbol.of("Person", 1);
142 var personView = new KeyOnlyView<>(person);
143
144 var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder
145 .clause(
146 personView.call(p1),
147 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, p1)
148 ));
149
150 var store = ModelStore.builder()
151 .symbols(person, friend)
152 .with(ViatraModelQueryAdapter.builder()
153 .queries(query))
154 .build();
155
156 var model = store.createEmptyModel();
157 var personInterpretation = model.getInterpretation(person);
158 var friendInterpretation = model.getInterpretation(friend);
159 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
160 var resultSet = queryEngine.getResultSet(query);
161
162 personInterpretation.put(Tuple.of(0), true);
163 personInterpretation.put(Tuple.of(1), true);
164 personInterpretation.put(Tuple.of(2), true);
165
166 friendInterpretation.put(Tuple.of(0, 1), true);
167 friendInterpretation.put(Tuple.of(1, 0), true);
168 friendInterpretation.put(Tuple.of(1, 2), true);
169 queryEngine.flushChanges();
170
171 assertThat(resultSet.size(), is(2));
172 assertThat(resultSet.get(Tuple.of(2)), is(true));
173 }
174
175 @Test
176 void diagonalDnfTest() {
177 var person = Symbol.of("Person", 1);
178 var personView = new KeyOnlyView<>(person);
179
180 var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder
181 .clause(
182 personView.call(p1),
183 personView.call(p2),
184 friendView.call(p1, p2)
185 ))
186 .getDnf();
187 var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder
188 .clause(
189 personView.call(p1),
190 new RepresentativeElectionLiteral(Connectivity.STRONG, subQuery, p1, p1)
191 ));
192
193 var store = ModelStore.builder()
194 .symbols(person, friend)
195 .with(ViatraModelQueryAdapter.builder()
196 .queries(query))
197 .build();
198
199 var model = store.createEmptyModel();
200 var personInterpretation = model.getInterpretation(person);
201 var friendInterpretation = model.getInterpretation(friend);
202 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
203 var resultSet = queryEngine.getResultSet(query);
204
205 personInterpretation.put(Tuple.of(0), true);
206 personInterpretation.put(Tuple.of(1), true);
207 personInterpretation.put(Tuple.of(2), true);
208
209 friendInterpretation.put(Tuple.of(0, 1), true);
210 friendInterpretation.put(Tuple.of(1, 0), true);
211 friendInterpretation.put(Tuple.of(1, 2), true);
212 queryEngine.flushChanges();
213
214 assertThat(resultSet.size(), is(2));
215 assertThat(resultSet.get(Tuple.of(2)), is(true));
216 }
217
218 @Test
219 void loopTest() {
220 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
221 .clause(v1 -> List.of(
222 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p1, v1),
223 new RepresentativeElectionLiteral(Connectivity.STRONG, friendView, p2, v1)
224 )));
225
226 var store = ModelStore.builder()
227 .symbols(friend)
228 .with(ViatraModelQueryAdapter.builder()
229 .queries(query))
230 .build();
231
232 var model = store.createEmptyModel();
233 var friendInterpretation = model.getInterpretation(friend);
234 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
235 var resultSet = queryEngine.getResultSet(query);
236
237 friendInterpretation.put(Tuple.of(0, 1), true);
238 friendInterpretation.put(Tuple.of(1, 2), true);
239 friendInterpretation.put(Tuple.of(2, 3), true);
240 friendInterpretation.put(Tuple.of(3, 0), true);
241 friendInterpretation.put(Tuple.of(3, 4), true);
242 queryEngine.flushChanges();
243
244 assertThat(resultSet.get(Tuple.of(0, 1)), is(true));
245 assertThat(resultSet.get(Tuple.of(1, 2)), is(true));
246 assertThat(resultSet.get(Tuple.of(2, 3)), is(true));
247 assertThat(resultSet.get(Tuple.of(3, 0)), is(true));
248 assertThat(resultSet.get(Tuple.of(3, 4)), is(false));
249
250 friendInterpretation.put(Tuple.of(2, 3), false);
251 queryEngine.flushChanges();
252
253 assertThat(resultSet.get(Tuple.of(0, 1)), is(false));
254 assertThat(resultSet.get(Tuple.of(0, 2)), is(false));
255 assertThat(resultSet.get(Tuple.of(0, 3)), is(false));
256 assertThat(resultSet.get(Tuple.of(1, 2)), is(false));
257 assertThat(resultSet.get(Tuple.of(2, 3)), is(false));
258 assertThat(resultSet.get(Tuple.of(3, 0)), is(false));
259 assertThat(resultSet.get(Tuple.of(3, 4)), is(false));
260 }
261}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/WeaklyConnectedComponentsTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/WeaklyConnectedComponentsTest.java
new file mode 100644
index 00000000..613d4284
--- /dev/null
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/WeaklyConnectedComponentsTest.java
@@ -0,0 +1,188 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.viatra;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.literal.Connectivity;
13import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple;
18
19import java.util.List;
20import java.util.Map;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
25
26class WeaklyConnectedComponentsTest {
27 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
28 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
29
30 @Test
31 void symbolViewTest() {
32 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
33 .clause(v1 -> List.of(
34 new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1),
35 new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1)
36 )));
37
38 var store = ModelStore.builder()
39 .symbols(friend)
40 .with(ViatraModelQueryAdapter.builder()
41 .queries(query))
42 .build();
43
44 var model = store.createEmptyModel();
45 var friendInterpretation = model.getInterpretation(friend);
46 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
47 var resultSet = queryEngine.getResultSet(query);
48
49 friendInterpretation.put(Tuple.of(0, 1), true);
50 friendInterpretation.put(Tuple.of(1, 0), true);
51 friendInterpretation.put(Tuple.of(2, 3), true);
52 queryEngine.flushChanges();
53
54 assertResults(Map.of(
55 Tuple.of(0, 0), true,
56 Tuple.of(0, 1), true,
57 Tuple.of(1, 0), true,
58 Tuple.of(1, 1), true,
59 Tuple.of(2, 2), true,
60 Tuple.of(2, 3), true,
61 Tuple.of(3, 2), true,
62 Tuple.of(3, 3), true
63 ), resultSet);
64 }
65
66 @Test
67 void symbolViewUpdateTest() {
68 var query = Query.of("SymbolViewRepresentative", (builder, p1, p2) -> builder
69 .clause(v1 -> List.of(
70 new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, v1),
71 new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p2, v1)
72 )));
73
74 var store = ModelStore.builder()
75 .symbols(friend)
76 .with(ViatraModelQueryAdapter.builder()
77 .queries(query))
78 .build();
79
80 var model = store.createEmptyModel();
81 var friendInterpretation = model.getInterpretation(friend);
82 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
83 var resultSet = queryEngine.getResultSet(query);
84
85 friendInterpretation.put(Tuple.of(0, 1), true);
86 friendInterpretation.put(Tuple.of(1, 0), true);
87 friendInterpretation.put(Tuple.of(2, 3), true);
88 queryEngine.flushChanges();
89
90 friendInterpretation.put(Tuple.of(2, 3), false);
91 friendInterpretation.put(Tuple.of(1, 0), false);
92 friendInterpretation.put(Tuple.of(1, 2), true);
93 queryEngine.flushChanges();
94
95 assertResults(Map.of(
96 Tuple.of(0, 0), true,
97 Tuple.of(0, 1), true,
98 Tuple.of(0, 2), true,
99 Tuple.of(1, 0), true,
100 Tuple.of(1, 1), true,
101 Tuple.of(1, 2), true,
102 Tuple.of(2, 0), true,
103 Tuple.of(2, 1), true,
104 Tuple.of(2, 2), true
105 ), resultSet);
106 }
107
108 @Test
109 void diagonalSymbolViewTest() {
110 var person = Symbol.of("Person", 1);
111 var personView = new KeyOnlyView<>(person);
112
113 var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder
114 .clause(
115 personView.call(p1),
116 new RepresentativeElectionLiteral(Connectivity.WEAK, friendView, p1, p1)
117 ));
118
119 var store = ModelStore.builder()
120 .symbols(person, friend)
121 .with(ViatraModelQueryAdapter.builder()
122 .queries(query))
123 .build();
124
125 var model = store.createEmptyModel();
126 var personInterpretation = model.getInterpretation(person);
127 var friendInterpretation = model.getInterpretation(friend);
128 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
129 var resultSet = queryEngine.getResultSet(query);
130
131 personInterpretation.put(Tuple.of(0), true);
132 personInterpretation.put(Tuple.of(1), true);
133 personInterpretation.put(Tuple.of(2), true);
134 personInterpretation.put(Tuple.of(3), true);
135
136 friendInterpretation.put(Tuple.of(0, 1), true);
137 friendInterpretation.put(Tuple.of(1, 0), true);
138 friendInterpretation.put(Tuple.of(2, 3), true);
139 queryEngine.flushChanges();
140
141 assertThat(resultSet.size(), is(2));
142 assertThat(resultSet.get(Tuple.of(2)), is(true));
143 }
144
145 @Test
146 void diagonalDnfTest() {
147 var person = Symbol.of("Person", 1);
148 var personView = new KeyOnlyView<>(person);
149
150 var subQuery = Query.of("SubQuery", (builder, p1, p2) -> builder
151 .clause(
152 personView.call(p1),
153 personView.call(p2),
154 friendView.call(p1, p2)
155 ))
156 .getDnf();
157 var query = Query.of("SymbolViewRepresentative", (builder, p1) -> builder
158 .clause(
159 personView.call(p1),
160 new RepresentativeElectionLiteral(Connectivity.WEAK, subQuery, p1, p1)
161 ));
162
163 var store = ModelStore.builder()
164 .symbols(person, friend)
165 .with(ViatraModelQueryAdapter.builder()
166 .queries(query))
167 .build();
168
169 var model = store.createEmptyModel();
170 var personInterpretation = model.getInterpretation(person);
171 var friendInterpretation = model.getInterpretation(friend);
172 var queryEngine = model.getAdapter(ModelQueryAdapter.class);
173 var resultSet = queryEngine.getResultSet(query);
174
175 personInterpretation.put(Tuple.of(0), true);
176 personInterpretation.put(Tuple.of(1), true);
177 personInterpretation.put(Tuple.of(2), true);
178 personInterpretation.put(Tuple.of(3), true);
179
180 friendInterpretation.put(Tuple.of(0, 1), true);
181 friendInterpretation.put(Tuple.of(1, 0), true);
182 friendInterpretation.put(Tuple.of(2, 3), true);
183 queryEngine.flushChanges();
184
185 assertThat(resultSet.size(), is(2));
186 assertThat(resultSet.get(Tuple.of(2)), is(true));
187 }
188}
diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java
index 968c6c5e..319797a0 100644
--- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java
+++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtilsTest.java
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.internal.matcher; 6package tools.refinery.store.query.viatra.internal.matcher;
7 7
8import org.eclipse.viatra.query.runtime.matchers.tuple.*; 8import tools.refinery.viatra.runtime.matchers.tuple.*;
9import org.junit.jupiter.api.Test; 9import org.junit.jupiter.api.Test;
10import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
11import tools.refinery.store.tuple.*; 11import tools.refinery.store.tuple.*;
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
index ca089a9d..5c1c4fc1 100644
--- 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
@@ -30,7 +30,7 @@ public final class QueryAssertions {
30 } 30 }
31 31
32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) { 32 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) {
33 var defaultValue = resultSet.getQuery().defaultValue(); 33 var defaultValue = resultSet.getCanonicalQuery().defaultValue();
34 var filteredExpected = new LinkedHashMap<Tuple, T>(); 34 var filteredExpected = new LinkedHashMap<Tuple, T>();
35 var executables = new ArrayList<Executable>(); 35 var executables = new ArrayList<Executable>();
36 for (var entry : expected.entrySet()) { 36 for (var entry : expected.entrySet()) {
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
index dc0e92c8..5f88e04b 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.tests; 6package tools.refinery.store.query.viatra.tests;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9 9
10/** 10/**
11 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. 11 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names.
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
index 9e75d5f3..ed356eeb 100644
--- 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
@@ -5,7 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.viatra.tests; 6package tools.refinery.store.query.viatra.tests;
7 7
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 8import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
9import org.junit.jupiter.api.extension.ExtensionContext; 9import org.junit.jupiter.api.extension.ExtensionContext;
10import org.junit.jupiter.params.provider.Arguments; 10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.ArgumentsProvider; 11import org.junit.jupiter.params.provider.ArgumentsProvider;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java
new file mode 100644
index 00000000..c39277a0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/InvalidQueryException.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query;
7
8public class InvalidQueryException extends RuntimeException {
9 public InvalidQueryException() {
10 }
11
12 public InvalidQueryException(String message) {
13 super(message);
14 }
15
16 public InvalidQueryException(String message, Throwable cause) {
17 super(message, cause);
18 }
19
20 public InvalidQueryException(Throwable cause) {
21 super(cause);
22 }
23}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
index c62a95b5..332e6381 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
@@ -8,6 +8,7 @@ package tools.refinery.store.query;
8import tools.refinery.store.adapter.ModelAdapterBuilder; 8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore; 9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery; 10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.rewriter.DnfRewriter;
11 12
12import java.util.Collection; 13import java.util.Collection;
13import java.util.List; 14import java.util.List;
@@ -25,6 +26,8 @@ public interface ModelQueryBuilder extends ModelAdapterBuilder {
25 26
26 ModelQueryBuilder query(AnyQuery query); 27 ModelQueryBuilder query(AnyQuery query);
27 28
29 ModelQueryBuilder rewriter(DnfRewriter rewriter);
30
28 @Override 31 @Override
29 ModelQueryStoreAdapter build(ModelStore store); 32 ModelQueryStoreAdapter build(ModelStore store);
30} 33}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
index f0a950a6..8b67c5c1 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -8,6 +8,7 @@ package tools.refinery.store.query;
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.dnf.AnyQuery; 10import tools.refinery.store.query.dnf.AnyQuery;
11import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.view.AnySymbolView; 12import tools.refinery.store.query.view.AnySymbolView;
12 13
13import java.util.Collection; 14import java.util.Collection;
@@ -17,6 +18,12 @@ public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
17 18
18 Collection<AnyQuery> getQueries(); 19 Collection<AnyQuery> getQueries();
19 20
21 default AnyQuery getCanonicalQuery(AnyQuery query) {
22 return getCanonicalQuery((Query<?>) query);
23 }
24
25 <T> Query<T> getCanonicalQuery(Query<T> query);
26
20 @Override 27 @Override
21 ModelQueryAdapter createModelAdapter(Model model); 28 ModelQueryAdapter createModelAdapter(Model model);
22} 29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
index b5e7092b..8800a155 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/ClausePostProcessor.java
@@ -6,13 +6,12 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.jetbrains.annotations.NotNull; 8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.query.literal.BooleanLiteral; 9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.literal.EquivalenceLiteral; 10import tools.refinery.store.query.InvalidQueryException;
11import tools.refinery.store.query.literal.Literal; 11import tools.refinery.store.query.literal.*;
12import tools.refinery.store.query.substitution.MapBasedSubstitution; 12import tools.refinery.store.query.substitution.MapBasedSubstitution;
13import tools.refinery.store.query.substitution.StatelessSubstitution; 13import tools.refinery.store.query.substitution.StatelessSubstitution;
14import tools.refinery.store.query.substitution.Substitution; 14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.NodeVariable;
16import tools.refinery.store.query.term.ParameterDirection; 15import tools.refinery.store.query.term.ParameterDirection;
17import tools.refinery.store.query.term.Variable; 16import tools.refinery.store.query.term.Variable;
18 17
@@ -22,8 +21,8 @@ import java.util.function.Function;
22class ClausePostProcessor { 21class ClausePostProcessor {
23 private final Map<Variable, ParameterInfo> parameters; 22 private final Map<Variable, ParameterInfo> parameters;
24 private final List<Literal> literals; 23 private final List<Literal> literals;
25 private final Map<NodeVariable, NodeVariable> representatives = new LinkedHashMap<>(); 24 private final Map<Variable, Variable> representatives = new LinkedHashMap<>();
26 private final Map<NodeVariable, Set<NodeVariable>> equivalencePartition = new HashMap<>(); 25 private final Map<Variable, Set<Variable>> equivalencePartition = new HashMap<>();
27 private List<Literal> substitutedLiterals; 26 private List<Literal> substitutedLiterals;
28 private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>(); 27 private final Set<Variable> existentiallyQuantifiedVariables = new LinkedHashSet<>();
29 private Set<Variable> positiveVariables; 28 private Set<Variable> positiveVariables;
@@ -58,6 +57,9 @@ class ClausePostProcessor {
58 if (filteredLiterals.isEmpty()) { 57 if (filteredLiterals.isEmpty()) {
59 return ConstantResult.ALWAYS_TRUE; 58 return ConstantResult.ALWAYS_TRUE;
60 } 59 }
60 if (hasContradictoryCall(filteredLiterals)) {
61 return ConstantResult.ALWAYS_FALSE;
62 }
61 var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables), 63 var clause = new DnfClause(Collections.unmodifiableSet(positiveVariables),
62 Collections.unmodifiableList(filteredLiterals)); 64 Collections.unmodifiableList(filteredLiterals));
63 return new ClauseResult(clause); 65 return new ClauseResult(clause);
@@ -67,16 +69,16 @@ class ClausePostProcessor {
67 for (var literal : literals) { 69 for (var literal : literals) {
68 if (isPositiveEquivalence(literal)) { 70 if (isPositiveEquivalence(literal)) {
69 var equivalenceLiteral = (EquivalenceLiteral) literal; 71 var equivalenceLiteral = (EquivalenceLiteral) literal;
70 mergeVariables(equivalenceLiteral.left(), equivalenceLiteral.right()); 72 mergeVariables(equivalenceLiteral.getLeft(), equivalenceLiteral.getRight());
71 } 73 }
72 } 74 }
73 } 75 }
74 76
75 private static boolean isPositiveEquivalence(Literal literal) { 77 private static boolean isPositiveEquivalence(Literal literal) {
76 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.positive(); 78 return literal instanceof EquivalenceLiteral equivalenceLiteral && equivalenceLiteral.isPositive();
77 } 79 }
78 80
79 private void mergeVariables(NodeVariable left, NodeVariable right) { 81 private void mergeVariables(Variable left, Variable right) {
80 var leftRepresentative = getRepresentative(left); 82 var leftRepresentative = getRepresentative(left);
81 var rightRepresentative = getRepresentative(right); 83 var rightRepresentative = getRepresentative(right);
82 var leftInfo = parameters.get(leftRepresentative); 84 var leftInfo = parameters.get(leftRepresentative);
@@ -89,7 +91,7 @@ class ClausePostProcessor {
89 } 91 }
90 } 92 }
91 93
92 private void doMergeVariables(NodeVariable parentRepresentative, NodeVariable newChildRepresentative) { 94 private void doMergeVariables(Variable parentRepresentative, Variable newChildRepresentative) {
93 var parentSet = getEquivalentVariables(parentRepresentative); 95 var parentSet = getEquivalentVariables(parentRepresentative);
94 var childSet = getEquivalentVariables(newChildRepresentative); 96 var childSet = getEquivalentVariables(newChildRepresentative);
95 parentSet.addAll(childSet); 97 parentSet.addAll(childSet);
@@ -99,18 +101,18 @@ class ClausePostProcessor {
99 } 101 }
100 } 102 }
101 103
102 private NodeVariable getRepresentative(NodeVariable variable) { 104 private Variable getRepresentative(Variable variable) {
103 return representatives.computeIfAbsent(variable, Function.identity()); 105 return representatives.computeIfAbsent(variable, Function.identity());
104 } 106 }
105 107
106 private Set<NodeVariable> getEquivalentVariables(NodeVariable variable) { 108 private Set<Variable> getEquivalentVariables(Variable variable) {
107 var representative = getRepresentative(variable); 109 var representative = getRepresentative(variable);
108 if (!representative.equals(variable)) { 110 if (!representative.equals(variable)) {
109 throw new AssertionError("NodeVariable %s already has a representative %s" 111 throw new AssertionError("NodeVariable %s already has a representative %s"
110 .formatted(variable, representative)); 112 .formatted(variable, representative));
111 } 113 }
112 return equivalencePartition.computeIfAbsent(variable, key -> { 114 return equivalencePartition.computeIfAbsent(variable, key -> {
113 var set = new HashSet<NodeVariable>(1); 115 var set = new HashSet<Variable>(1);
114 set.add(key); 116 set.add(key);
115 return set; 117 return set;
116 }); 118 });
@@ -121,7 +123,7 @@ class ClausePostProcessor {
121 var left = pair.getKey(); 123 var left = pair.getKey();
122 var right = pair.getValue(); 124 var right = pair.getValue();
123 if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) { 125 if (!left.equals(right) && parameters.containsKey(left) && parameters.containsKey(right)) {
124 substitutedLiterals.add(left.isEquivalent(right)); 126 substitutedLiterals.add(new EquivalenceLiteral(true, left, right));
125 } 127 }
126 } 128 }
127 } 129 }
@@ -147,20 +149,7 @@ class ClausePostProcessor {
147 149
148 private void computeExistentiallyQuantifiedVariables() { 150 private void computeExistentiallyQuantifiedVariables() {
149 for (var literal : substitutedLiterals) { 151 for (var literal : substitutedLiterals) {
150 for (var variable : literal.getOutputVariables()) { 152 existentiallyQuantifiedVariables.addAll(literal.getOutputVariables());
151 boolean added = existentiallyQuantifiedVariables.add(variable);
152 if (!variable.isUnifiable()) {
153 var parameterInfo = parameters.get(variable);
154 if (parameterInfo != null && parameterInfo.direction() == ParameterDirection.IN) {
155 throw new IllegalArgumentException("Trying to bind %s parameter %s"
156 .formatted(ParameterDirection.IN, variable));
157 }
158 if (!added) {
159 throw new IllegalArgumentException("Variable %s has multiple assigned values"
160 .formatted(variable));
161 }
162 }
163 }
164 } 153 }
165 } 154 }
166 155
@@ -172,7 +161,7 @@ class ClausePostProcessor {
172 // Inputs count as positive, because they are already bound when we evaluate literals. 161 // Inputs count as positive, because they are already bound when we evaluate literals.
173 positiveVariables.add(variable); 162 positiveVariables.add(variable);
174 } else if (!existentiallyQuantifiedVariables.contains(variable)) { 163 } else if (!existentiallyQuantifiedVariables.contains(variable)) {
175 throw new IllegalArgumentException("Unbound %s parameter %s" 164 throw new InvalidQueryException("Unbound %s parameter %s"
176 .formatted(ParameterDirection.OUT, variable)); 165 .formatted(ParameterDirection.OUT, variable));
177 } 166 }
178 } 167 }
@@ -184,7 +173,7 @@ class ClausePostProcessor {
184 var representative = pair.getKey(); 173 var representative = pair.getKey();
185 if (!positiveVariables.contains(representative)) { 174 if (!positiveVariables.contains(representative)) {
186 var variableSet = pair.getValue(); 175 var variableSet = pair.getValue();
187 throw new IllegalArgumentException("Variables %s were merged by equivalence but are not bound" 176 throw new InvalidQueryException("Variables %s were merged by equivalence but are not bound"
188 .formatted(variableSet)); 177 .formatted(variableSet));
189 } 178 }
190 } 179 }
@@ -196,7 +185,7 @@ class ClausePostProcessor {
196 for (var variable : literal.getPrivateVariables(positiveVariables)) { 185 for (var variable : literal.getPrivateVariables(positiveVariables)) {
197 var oldLiteral = negativeVariablesMap.put(variable, literal); 186 var oldLiteral = negativeVariablesMap.put(variable, literal);
198 if (oldLiteral != null) { 187 if (oldLiteral != null) {
199 throw new IllegalArgumentException("Unbound variable %s appears in multiple literals %s and %s" 188 throw new InvalidQueryException("Unbound variable %s appears in multiple literals %s and %s"
200 .formatted(variable, oldLiteral, literal)); 189 .formatted(variable, oldLiteral, literal));
201 } 190 }
202 } 191 }
@@ -218,11 +207,60 @@ class ClausePostProcessor {
218 variable.addToSortedLiterals(); 207 variable.addToSortedLiterals();
219 } 208 }
220 if (!variableToLiteralInputMap.isEmpty()) { 209 if (!variableToLiteralInputMap.isEmpty()) {
221 throw new IllegalArgumentException("Unbound input variables %s" 210 throw new InvalidQueryException("Unbound input variables %s"
222 .formatted(variableToLiteralInputMap.keySet())); 211 .formatted(variableToLiteralInputMap.keySet()));
223 } 212 }
224 } 213 }
225 214
215 private boolean hasContradictoryCall(Collection<Literal> filteredLiterals) {
216 var positiveCalls = new HashMap<Constraint, Set<CallLiteral>>();
217 for (var literal : filteredLiterals) {
218 if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.POSITIVE) {
219 var callsOfTarget = positiveCalls.computeIfAbsent(callLiteral.getTarget(), key -> new HashSet<>());
220 callsOfTarget.add(callLiteral);
221 }
222 }
223 for (var literal : filteredLiterals) {
224 if (literal instanceof CallLiteral callLiteral && callLiteral.getPolarity() == CallPolarity.NEGATIVE) {
225 var callsOfTarget = positiveCalls.get(callLiteral.getTarget());
226 if (contradicts(callLiteral, callsOfTarget)) {
227 return true;
228 }
229 }
230 }
231 return false;
232 }
233
234 private boolean contradicts(CallLiteral negativeCall, Collection<CallLiteral> positiveCalls) {
235 if (positiveCalls == null) {
236 return false;
237 }
238 for (var positiveCall : positiveCalls) {
239 if (contradicts(negativeCall, positiveCall)) {
240 return true;
241 }
242 }
243 return false;
244 }
245
246 private boolean contradicts(CallLiteral negativeCall, CallLiteral positiveCall) {
247 var privateVariables = negativeCall.getPrivateVariables(positiveVariables);
248 var negativeArguments = negativeCall.getArguments();
249 var positiveArguments = positiveCall.getArguments();
250 int arity = negativeArguments.size();
251 for (int i = 0; i < arity; i++) {
252 var negativeArgument = negativeArguments.get(i);
253 if (privateVariables.contains(negativeArgument)) {
254 continue;
255 }
256 var positiveArgument = positiveArguments.get(i);
257 if (!negativeArgument.equals(positiveArgument)) {
258 return false;
259 }
260 }
261 return true;
262 }
263
226 private class SortableLiteral implements Comparable<SortableLiteral> { 264 private class SortableLiteral implements Comparable<SortableLiteral> {
227 private final int index; 265 private final int index;
228 private final Literal literal; 266 private final Literal literal;
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
index 50b245f7..86a1b6b2 100644
--- 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
@@ -6,9 +6,12 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.Reduction; 9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.DnfEqualityChecker; 10import tools.refinery.store.query.equality.DnfEqualityChecker;
11import tools.refinery.store.query.equality.LiteralEqualityHelper; 11import tools.refinery.store.query.equality.LiteralEqualityHelper;
12import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
13import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper;
14import tools.refinery.store.query.literal.Reduction;
12import tools.refinery.store.query.term.Parameter; 15import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.term.Variable; 16import tools.refinery.store.query.term.Variable;
14 17
@@ -53,7 +56,7 @@ public final class Dnf implements Constraint {
53 FunctionalDependency<Variable> functionalDependency) { 56 FunctionalDependency<Variable> functionalDependency) {
54 for (var variable : toValidate) { 57 for (var variable : toValidate) {
55 if (!parameterSet.contains(variable)) { 58 if (!parameterSet.contains(variable)) {
56 throw new IllegalArgumentException( 59 throw new InvalidQueryException(
57 "Variable %s of functional dependency %s does not appear in the parameter list %s" 60 "Variable %s of functional dependency %s does not appear in the parameter list %s"
58 .formatted(variable, functionalDependency, symbolicParameters)); 61 .formatted(variable, functionalDependency, symbolicParameters));
59 } 62 }
@@ -66,7 +69,7 @@ public final class Dnf implements Constraint {
66 } 69 }
67 70
68 public boolean isExplicitlyNamed() { 71 public boolean isExplicitlyNamed() {
69 return name == null; 72 return name != null;
70 } 73 }
71 74
72 public String getUniqueName() { 75 public String getUniqueName() {
@@ -129,7 +132,7 @@ public final class Dnf implements Constraint {
129 return false; 132 return false;
130 } 133 }
131 for (int i = 0; i < numClauses; i++) { 134 for (int i = 0; i < numClauses; i++) {
132 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, symbolicParameters, 135 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(callEqualityChecker, symbolicParameters,
133 other.symbolicParameters); 136 other.symbolicParameters);
134 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { 137 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
135 return false; 138 return false;
@@ -146,6 +149,18 @@ public final class Dnf implements Constraint {
146 return false; 149 return false;
147 } 150 }
148 151
152 public int hashCodeWithSubstitution() {
153 var helper = new SubstitutingLiteralHashCodeHelper();
154 int result = 0;
155 for (var symbolicParameter : symbolicParameters) {
156 result = result * 31 + symbolicParameter.hashCodeWithSubstitution(helper);
157 }
158 for (var clause : clauses) {
159 result = result * 31 + clause.hashCodeWithSubstitution(helper);
160 }
161 return result;
162 }
163
149 @Override 164 @Override
150 public String toString() { 165 public String toString() {
151 return "%s/%d".formatted(name(), arity()); 166 return "%s/%d".formatted(name(), arity());
@@ -201,6 +216,13 @@ public final class Dnf implements Constraint {
201 return new DnfBuilder(name); 216 return new DnfBuilder(name);
202 } 217 }
203 218
219 public static DnfBuilder builderFrom(Dnf original) {
220 var builder = builder(original.name());
221 builder.symbolicParameters(original.getSymbolicParameters());
222 builder.functionalDependencies(original.getFunctionalDependencies());
223 return builder;
224 }
225
204 public static Dnf of(Consumer<DnfBuilder> callback) { 226 public static Dnf of(Consumer<DnfBuilder> callback) {
205 return of(null, callback); 227 return of(null, callback);
206 } 228 }
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
index 8e38ca6b..0f9fd366 100644
--- 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
@@ -5,12 +5,10 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.dnf.callback.*; 9import tools.refinery.store.query.dnf.callback.*;
9import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.DataVariable; 11import tools.refinery.store.query.term.*;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14 12
15import java.util.*; 13import java.util.*;
16 14
@@ -62,6 +60,18 @@ public final class DnfBuilder {
62 return variable; 60 return variable;
63 } 61 }
64 62
63 public Variable parameter(Parameter parameter) {
64 return parameter(null, parameter);
65 }
66
67 public Variable parameter(String name, Parameter parameter) {
68 var type = parameter.tryGetType();
69 if (type.isPresent()) {
70 return parameter(name, type.get(), parameter.getDirection());
71 }
72 return parameter(name, parameter.getDirection());
73 }
74
65 public DnfBuilder parameter(Variable variable) { 75 public DnfBuilder parameter(Variable variable) {
66 return parameter(variable, ParameterDirection.OUT); 76 return parameter(variable, ParameterDirection.OUT);
67 } 77 }
@@ -88,7 +98,7 @@ public final class DnfBuilder {
88 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) { 98 public DnfBuilder symbolicParameter(SymbolicParameter symbolicParameter) {
89 var variable = symbolicParameter.getVariable(); 99 var variable = symbolicParameter.getVariable();
90 if (!parameterVariables.add(variable)) { 100 if (!parameterVariables.add(variable)) {
91 throw new IllegalArgumentException("Variable %s is already on the parameter list %s" 101 throw new InvalidQueryException("Variable %s is already on the parameter list %s"
92 .formatted(variable, parameters)); 102 .formatted(variable, parameters));
93 } 103 }
94 parameters.add(symbolicParameter); 104 parameters.add(symbolicParameter);
@@ -129,7 +139,7 @@ public final class DnfBuilder {
129 } 139 }
130 140
131 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) { 141 public <T> DnfBuilder clause(Class<T> type1, ClauseCallback1Data1<T> callback) {
132 return clause(callback.toLiterals(Variable.of("v1", type1))); 142 return clause(callback.toLiterals(Variable.of("d1", type1)));
133 } 143 }
134 144
135 public DnfBuilder clause(ClauseCallback2Data0 callback) { 145 public DnfBuilder clause(ClauseCallback2Data0 callback) {
@@ -206,57 +216,10 @@ public final class DnfBuilder {
206 } 216 }
207 217
208 public Dnf build() { 218 public Dnf build() {
209 var postProcessedClauses = postProcessClauses(); 219 var postProcessor = new DnfPostProcessor(parameters, clauses);
220 var postProcessedClauses = postProcessor.postProcessClauses();
210 return new Dnf(name, Collections.unmodifiableList(parameters), 221 return new Dnf(name, Collections.unmodifiableList(parameters),
211 Collections.unmodifiableList(functionalDependencies), 222 Collections.unmodifiableList(functionalDependencies),
212 Collections.unmodifiableList(postProcessedClauses)); 223 Collections.unmodifiableList(postProcessedClauses));
213 } 224 }
214
215 private List<DnfClause> postProcessClauses() {
216 var parameterInfoMap = getParameterInfoMap();
217 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
218 for (var literals : clauses) {
219 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
220 var result = postProcessor.postProcessClause();
221 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
222 postProcessedClauses.add(clauseResult.clause());
223 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
224 switch (constantResult) {
225 case ALWAYS_TRUE -> {
226 var inputVariables = getInputVariables();
227 return List.of(new DnfClause(inputVariables, List.of()));
228 }
229 case ALWAYS_FALSE -> {
230 // Skip this clause because it can never match.
231 }
232 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
233 constantResult);
234 }
235 } else {
236 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
237 }
238 }
239 return postProcessedClauses;
240 }
241
242 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
243 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
244 int arity = parameters.size();
245 for (int i = 0; i < arity; i++) {
246 var parameter = parameters.get(i);
247 mutableParameterInfoMap.put(parameter.getVariable(),
248 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
249 }
250 return Collections.unmodifiableMap(mutableParameterInfoMap);
251 }
252
253 private Set<Variable> getInputVariables() {
254 var inputParameters = new LinkedHashSet<Variable>();
255 for (var parameter : parameters) {
256 if (parameter.getDirection() == ParameterDirection.IN) {
257 inputParameters.add(parameter.getVariable());
258 }
259 }
260 return Collections.unmodifiableSet(inputParameters);
261 }
262} 225}
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
index fdd0d47c..94327bad 100644
--- 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
11 12
@@ -25,4 +26,12 @@ public record DnfClause(Set<Variable> positiveVariables, List<Literal> literals)
25 } 26 }
26 return true; 27 return true;
27 } 28 }
29
30 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
31 int result = 0;
32 for (var literal : literals) {
33 result = result * 31 + literal.hashCodeWithSubstitution(helper);
34 }
35 return result;
36 }
28} 37}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
new file mode 100644
index 00000000..50236642
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfPostProcessor.java
@@ -0,0 +1,112 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
11import tools.refinery.store.query.equality.SubstitutingLiteralHashCodeHelper;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.*;
17
18class DnfPostProcessor {
19 private final List<SymbolicParameter> parameters;
20 private final List<List<Literal>> clauses;
21
22 public DnfPostProcessor(List<SymbolicParameter> parameters, List<List<Literal>> clauses) {
23 this.parameters = parameters;
24 this.clauses = clauses;
25 }
26
27 public List<DnfClause> postProcessClauses() {
28 var parameterInfoMap = getParameterInfoMap();
29 var postProcessedClauses = new LinkedHashSet<CanonicalClause>(clauses.size());
30 int index = 0;
31 for (var literals : clauses) {
32 var postProcessor = new ClausePostProcessor(parameterInfoMap, literals);
33 ClausePostProcessor.Result result;
34 try {
35 result = postProcessor.postProcessClause();
36 } catch (InvalidQueryException e) {
37 throw new InvalidClauseException(index, e);
38 }
39 if (result instanceof ClausePostProcessor.ClauseResult clauseResult) {
40 postProcessedClauses.add(new CanonicalClause(clauseResult.clause()));
41 } else if (result instanceof ClausePostProcessor.ConstantResult constantResult) {
42 switch (constantResult) {
43 case ALWAYS_TRUE -> {
44 var inputVariables = getInputVariables();
45 return List.of(new DnfClause(inputVariables, List.of()));
46 }
47 case ALWAYS_FALSE -> {
48 // Skip this clause because it can never match.
49 }
50 default -> throw new IllegalStateException("Unexpected ClausePostProcessor.ConstantResult: " +
51 constantResult);
52 }
53 } else {
54 throw new IllegalStateException("Unexpected ClausePostProcessor.Result: " + result);
55 }
56 index++;
57 }
58 return postProcessedClauses.stream().map(CanonicalClause::getDnfClause).toList();
59 }
60
61 private Map<Variable, ClausePostProcessor.ParameterInfo> getParameterInfoMap() {
62 var mutableParameterInfoMap = new LinkedHashMap<Variable, ClausePostProcessor.ParameterInfo>();
63 int arity = parameters.size();
64 for (int i = 0; i < arity; i++) {
65 var parameter = parameters.get(i);
66 mutableParameterInfoMap.put(parameter.getVariable(),
67 new ClausePostProcessor.ParameterInfo(parameter.getDirection(), i));
68 }
69 return Collections.unmodifiableMap(mutableParameterInfoMap);
70 }
71
72 private Set<Variable> getInputVariables() {
73 var inputParameters = new LinkedHashSet<Variable>();
74 for (var parameter : parameters) {
75 if (parameter.getDirection() == ParameterDirection.IN) {
76 inputParameters.add(parameter.getVariable());
77 }
78 }
79 return Collections.unmodifiableSet(inputParameters);
80 }
81
82 private class CanonicalClause {
83 private final DnfClause dnfClause;
84
85 public CanonicalClause(DnfClause dnfClause) {
86 this.dnfClause = dnfClause;
87 }
88
89 public DnfClause getDnfClause() {
90 return dnfClause;
91 }
92
93 @Override
94 public boolean equals(Object obj) {
95 if (this == obj) {
96 return true;
97 }
98 if (obj == null || getClass() != obj.getClass()) {
99 return false;
100 }
101 var otherCanonicalClause = (CanonicalClause) obj;
102 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, parameters, parameters);
103 return dnfClause.equalsWithSubstitution(helper, otherCanonicalClause.dnfClause);
104 }
105
106 @Override
107 public int hashCode() {
108 var helper = new SubstitutingLiteralHashCodeHelper(parameters);
109 return dnfClause.hashCodeWithSubstitution(helper);
110 }
111 }
112}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
index b00b2cb7..aef07ee3 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
9
8import java.util.HashSet; 10import java.util.HashSet;
9import java.util.Set; 11import java.util.Set;
10 12
@@ -13,7 +15,7 @@ public record FunctionalDependency<T>(Set<T> forEach, Set<T> unique) {
13 var uniqueForEach = new HashSet<>(unique); 15 var uniqueForEach = new HashSet<>(unique);
14 uniqueForEach.retainAll(forEach); 16 uniqueForEach.retainAll(forEach);
15 if (!uniqueForEach.isEmpty()) { 17 if (!uniqueForEach.isEmpty()) {
16 throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" 18 throw new InvalidQueryException("Variables %s appear on both sides of the functional dependency"
17 .formatted(uniqueForEach)); 19 .formatted(uniqueForEach));
18 } 20 }
19 } 21 }
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
index 5a32b1ba..225f6844 100644
--- 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
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallPolarity; 9import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.term.Aggregator; 10import tools.refinery.store.query.term.Aggregator;
10import tools.refinery.store.query.term.AssignedValue; 11import tools.refinery.store.query.term.AssignedValue;
@@ -26,14 +27,14 @@ public final class FunctionalQuery<T> extends Query<T> {
26 var parameter = parameters.get(i); 27 var parameter = parameters.get(i);
27 var parameterType = parameter.tryGetType(); 28 var parameterType = parameter.tryGetType();
28 if (parameterType.isPresent()) { 29 if (parameterType.isPresent()) {
29 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" 30 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
30 .formatted(parameter, dnf, parameterType.get().getName())); 31 .formatted(parameter, dnf, parameterType.get().getName()));
31 } 32 }
32 } 33 }
33 var outputParameter = parameters.get(outputIndex); 34 var outputParameter = parameters.get(outputIndex);
34 var outputParameterType = outputParameter.tryGetType(); 35 var outputParameterType = outputParameter.tryGetType();
35 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) { 36 if (outputParameterType.isEmpty() || !outputParameterType.get().equals(type)) {
36 throw new IllegalArgumentException("Expected parameter %s of %s to be %s, but got %s instead".formatted( 37 throw new InvalidQueryException("Expected parameter %s of %s to be %s, but got %s instead".formatted(
37 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node"))); 38 outputParameter, dnf, type, outputParameterType.map(Class::getName).orElse("node")));
38 } 39 }
39 this.type = type; 40 this.type = type;
@@ -54,6 +55,16 @@ public final class FunctionalQuery<T> extends Query<T> {
54 return null; 55 return null;
55 } 56 }
56 57
58 @Override
59 protected FunctionalQuery<T> withDnfInternal(Dnf newDnf) {
60 return newDnf.asFunction(type);
61 }
62
63 @Override
64 public FunctionalQuery<T> withDnf(Dnf newDnf) {
65 return (FunctionalQuery<T>) super.withDnf(newDnf);
66 }
67
57 public AssignedValue<T> call(List<NodeVariable> arguments) { 68 public AssignedValue<T> call(List<NodeVariable> arguments) {
58 return targetVariable -> { 69 return targetVariable -> {
59 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1); 70 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java
new file mode 100644
index 00000000..747574b9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/InvalidClauseException.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import tools.refinery.store.query.InvalidQueryException;
9
10public class InvalidClauseException extends InvalidQueryException {
11 private final int clauseIndex;
12
13 public InvalidClauseException(int clauseIndex) {
14 this.clauseIndex = clauseIndex;
15 }
16
17 public InvalidClauseException(int clauseIndex, String message) {
18 super(message);
19 this.clauseIndex = clauseIndex;
20 }
21
22 public InvalidClauseException(int clauseIndex, String message, Throwable cause) {
23 super(message, cause);
24 this.clauseIndex = clauseIndex;
25 }
26
27 public InvalidClauseException(int clauseIndex, Throwable cause) {
28 super(cause);
29 this.clauseIndex = clauseIndex;
30 }
31
32 public int getClauseIndex() {
33 return clauseIndex;
34 }
35}
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
index aaa52ce6..83fe6ccd 100644
--- 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
@@ -43,6 +43,29 @@ public abstract sealed class Query<T> implements AnyQuery permits FunctionalQuer
43 43
44 public abstract T defaultValue(); 44 public abstract T defaultValue();
45 45
46 public Query<T> withDnf(Dnf newDnf) {
47 if (dnf.equals(newDnf)) {
48 return this;
49 }
50 int arity = dnf.arity();
51 if (newDnf.arity() != arity) {
52 throw new IllegalArgumentException("Arity of %s and %s do not match".formatted(dnf, newDnf));
53 }
54 var parameters = dnf.getParameters();
55 var newParameters = newDnf.getParameters();
56 for (int i = 0; i < arity; i++) {
57 var parameter = parameters.get(i);
58 var newParameter = newParameters.get(i);
59 if (!parameter.matches(newParameter)) {
60 throw new IllegalArgumentException("Parameter #%d mismatch: %s does not match %s"
61 .formatted(i, parameter, newParameter));
62 }
63 }
64 return withDnfInternal(newDnf);
65 }
66
67 protected abstract Query<T> withDnfInternal(Dnf newDnf);
68
46 @Override 69 @Override
47 public boolean equals(Object o) { 70 public boolean equals(Object o) {
48 if (this == o) return true; 71 if (this == o) return true;
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
index d34a7ace..98f71e11 100644
--- 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
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallLiteral; 9import tools.refinery.store.query.literal.CallLiteral;
9import tools.refinery.store.query.literal.CallPolarity; 10import tools.refinery.store.query.literal.CallPolarity;
10import tools.refinery.store.query.term.AssignedValue; 11import tools.refinery.store.query.term.AssignedValue;
@@ -19,7 +20,7 @@ public final class RelationalQuery extends Query<Boolean> {
19 for (var parameter : dnf.getSymbolicParameters()) { 20 for (var parameter : dnf.getSymbolicParameters()) {
20 var parameterType = parameter.tryGetType(); 21 var parameterType = parameter.tryGetType();
21 if (parameterType.isPresent()) { 22 if (parameterType.isPresent()) {
22 throw new IllegalArgumentException("Expected parameter %s of %s to be a node variable, got %s instead" 23 throw new InvalidQueryException("Expected parameter %s of %s to be a node variable, got %s instead"
23 .formatted(parameter, dnf, parameterType.get().getName())); 24 .formatted(parameter, dnf, parameterType.get().getName()));
24 } 25 }
25 } 26 }
@@ -40,6 +41,16 @@ public final class RelationalQuery extends Query<Boolean> {
40 return false; 41 return false;
41 } 42 }
42 43
44 @Override
45 protected RelationalQuery withDnfInternal(Dnf newDnf) {
46 return newDnf.asRelation();
47 }
48
49 @Override
50 public RelationalQuery withDnf(Dnf newDnf) {
51 return (RelationalQuery) super.withDnf(newDnf);
52 }
53
43 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) { 54 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
44 return getDnf().call(polarity, Collections.unmodifiableList(arguments)); 55 return getDnf().call(polarity, Collections.unmodifiableList(arguments));
45 } 56 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
index e0d3ba1f..fe9cefcc 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/SymbolicParameter.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import tools.refinery.store.query.equality.LiteralHashCodeHelper;
8import tools.refinery.store.query.term.Parameter; 9import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.term.ParameterDirection; 10import tools.refinery.store.query.term.ParameterDirection;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
@@ -23,8 +24,8 @@ public final class SymbolicParameter extends Parameter {
23 return variable; 24 return variable;
24 } 25 }
25 26
26 public boolean isUnifiable() { 27 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
27 return variable.isUnifiable(); 28 return Objects.hash(super.hashCode(), helper.getVariableHashCode(variable));
28 } 29 }
29 30
30 @Override 31 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
index 1eeb5723..d6171314 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -14,8 +14,7 @@ import tools.refinery.store.util.CycleDetectingMapper;
14import java.util.List; 14import java.util.List;
15 15
16public class DeepDnfEqualityChecker implements DnfEqualityChecker { 16public class DeepDnfEqualityChecker implements DnfEqualityChecker {
17 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString, 17 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(this::doCheckEqual);
18 this::doCheckEqual);
19 18
20 @Override 19 @Override
21 public boolean dnfEqual(Dnf left, Dnf right) { 20 public boolean dnfEqual(Dnf left, Dnf right) {
@@ -38,7 +37,7 @@ public class DeepDnfEqualityChecker implements DnfEqualityChecker {
38 return false; 37 return false;
39 } 38 }
40 for (int i = 0; i < numClauses; i++) { 39 for (int i = 0; i < numClauses; i++) {
41 var literalEqualityHelper = new LiteralEqualityHelper(this, symbolicParameters, 40 var literalEqualityHelper = new SubstitutingLiteralEqualityHelper(this, symbolicParameters,
42 other.getSymbolicParameters()); 41 other.getSymbolicParameters());
43 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) { 42 if (!equalsWithSubstitutionRaw(literalEqualityHelper, clauses.get(i), other.getClauses().get(i))) {
44 return false; 43 return false;
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
index 4a8bee3b..e2cfd79b 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
@@ -7,7 +7,11 @@ package tools.refinery.store.query.equality;
7 7
8import tools.refinery.store.query.dnf.Dnf; 8import tools.refinery.store.query.dnf.Dnf;
9 9
10import java.util.Objects;
11
10@FunctionalInterface 12@FunctionalInterface
11public interface DnfEqualityChecker { 13public interface DnfEqualityChecker {
14 DnfEqualityChecker DEFAULT = Objects::equals;
15
12 boolean dnfEqual(Dnf left, Dnf right); 16 boolean dnfEqual(Dnf left, Dnf right);
13} 17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
index 9315fb30..5abc76ce 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
@@ -6,49 +6,22 @@
6package tools.refinery.store.query.equality; 6package tools.refinery.store.query.equality;
7 7
8import tools.refinery.store.query.dnf.Dnf; 8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable; 9import tools.refinery.store.query.term.Variable;
11 10
12import java.util.HashMap; 11import java.util.Objects;
13import java.util.List;
14import java.util.Map;
15 12
16public class LiteralEqualityHelper { 13public interface LiteralEqualityHelper extends DnfEqualityChecker {
17 private final DnfEqualityChecker dnfEqualityChecker; 14 LiteralEqualityHelper DEFAULT = new LiteralEqualityHelper() {
18 private final Map<Variable, Variable> leftToRight; 15 @Override
19 private final Map<Variable, Variable> rightToLeft; 16 public boolean variableEqual(Variable left, Variable right) {
20 17 return Objects.equals(left, right);
21 public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<SymbolicParameter> leftParameters,
22 List<SymbolicParameter> rightParameters) {
23 this.dnfEqualityChecker = dnfEqualityChecker;
24 var arity = leftParameters.size();
25 if (arity != rightParameters.size()) {
26 throw new IllegalArgumentException("Parameter lists have unequal length");
27 }
28 leftToRight = new HashMap<>(arity);
29 rightToLeft = new HashMap<>(arity);
30 for (int i = 0; i < arity; i++) {
31 if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) {
32 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
33 }
34 } 18 }
35 }
36
37 public boolean dnfEqual(Dnf left, Dnf right) {
38 return dnfEqualityChecker.dnfEqual(left, right);
39 }
40 19
41 public boolean variableEqual(Variable left, Variable right) { 20 @Override
42 if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) { 21 public boolean dnfEqual(Dnf left, Dnf right) {
43 leftToRight.put(left, right); 22 return DnfEqualityChecker.DEFAULT.dnfEqual(left, right);
44 rightToLeft.put(right, left);
45 return true;
46 } 23 }
47 return false; 24 };
48 }
49 25
50 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) { 26 boolean variableEqual(Variable left, Variable right);
51 var currentValue = map.get(key);
52 return currentValue == null || currentValue.equals(expectedValue);
53 }
54} 27}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java
new file mode 100644
index 00000000..5495160a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralHashCodeHelper.java
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Objects;
11
12@FunctionalInterface
13public interface LiteralHashCodeHelper {
14 LiteralHashCodeHelper DEFAULT = Objects::hashCode;
15
16 int getVariableHashCode(Variable variable);
17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java
new file mode 100644
index 00000000..50a79e07
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralEqualityHelper.java
@@ -0,0 +1,59 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.SymbolicParameter;
10import tools.refinery.store.query.term.Variable;
11
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15
16public class SubstitutingLiteralEqualityHelper implements LiteralEqualityHelper {
17 private final DnfEqualityChecker dnfEqualityChecker;
18 private final Map<Variable, Variable> leftToRight;
19 private final Map<Variable, Variable> rightToLeft;
20
21 public SubstitutingLiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker,
22 List<SymbolicParameter> leftParameters,
23 List<SymbolicParameter> rightParameters) {
24 this.dnfEqualityChecker = dnfEqualityChecker;
25 var arity = leftParameters.size();
26 if (arity != rightParameters.size()) {
27 throw new IllegalArgumentException("Parameter lists have unequal length");
28 }
29 leftToRight = new HashMap<>(arity);
30 rightToLeft = new HashMap<>(arity);
31 for (int i = 0; i < arity; i++) {
32 if (!variableEqual(leftParameters.get(i).getVariable(), rightParameters.get(i).getVariable())) {
33 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
34 }
35 }
36 }
37
38 @Override
39 public boolean dnfEqual(Dnf left, Dnf right) {
40 return dnfEqualityChecker.dnfEqual(left, right);
41 }
42
43 @Override
44 public boolean variableEqual(Variable left, Variable right) {
45 if (left.tryGetType().equals(right.tryGetType()) &&
46 checkMapping(leftToRight, left, right) &&
47 checkMapping(rightToLeft, right, left)) {
48 leftToRight.put(left, right);
49 rightToLeft.put(right, left);
50 return true;
51 }
52 return false;
53 }
54
55 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
56 var currentValue = map.get(key);
57 return currentValue == null || currentValue.equals(expectedValue);
58 }
59}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java
new file mode 100644
index 00000000..754f6976
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/SubstitutingLiteralHashCodeHelper.java
@@ -0,0 +1,42 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.equality;
7
8import tools.refinery.store.query.dnf.SymbolicParameter;
9import tools.refinery.store.query.term.Variable;
10
11import java.util.LinkedHashMap;
12import java.util.List;
13import java.util.Map;
14
15public class SubstitutingLiteralHashCodeHelper implements LiteralHashCodeHelper {
16 private final Map<Variable, Integer> assignedHashCodes = new LinkedHashMap<>();
17
18 // 0 is for {@code null}, so we start with 1.
19 private int next = 1;
20
21 public SubstitutingLiteralHashCodeHelper() {
22 this(List.of());
23 }
24
25 public SubstitutingLiteralHashCodeHelper(List<SymbolicParameter> parameters) {
26 for (var parameter : parameters) {
27 getVariableHashCode(parameter.getVariable());
28 }
29 }
30
31 @Override
32 public int getVariableHashCode(Variable variable) {
33 if (variable == null) {
34 return 0;
35 }
36 return assignedHashCodes.computeIfAbsent(variable, key -> {
37 int sequenceNumber = next;
38 next++;
39 return variable.hashCodeWithSubstitution(sequenceNumber);
40 });
41 }
42}
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
index 8ef8e8b4..0e99d441 100644
--- 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
@@ -6,14 +6,18 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 13import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
13 15
14import java.util.*; 16import java.util.*;
15 17
16public abstract class AbstractCallLiteral implements Literal { 18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
20public abstract class AbstractCallLiteral extends AbstractLiteral {
17 private final Constraint target; 21 private final Constraint target;
18 private final List<Variable> arguments; 22 private final List<Variable> arguments;
19 private final Set<Variable> inArguments; 23 private final Set<Variable> inArguments;
@@ -24,7 +28,7 @@ public abstract class AbstractCallLiteral implements Literal {
24 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) { 28 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
25 int arity = target.arity(); 29 int arity = target.arity();
26 if (arguments.size() != arity) { 30 if (arguments.size() != arity) {
27 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), 31 throw new InvalidQueryException("%s needs %d arguments, but got %s".formatted(target.name(),
28 target.arity(), arguments.size())); 32 target.arity(), arguments.size()));
29 } 33 }
30 this.target = target; 34 this.target = target;
@@ -36,21 +40,17 @@ public abstract class AbstractCallLiteral implements Literal {
36 var argument = arguments.get(i); 40 var argument = arguments.get(i);
37 var parameter = parameters.get(i); 41 var parameter = parameters.get(i);
38 if (!parameter.isAssignable(argument)) { 42 if (!parameter.isAssignable(argument)) {
39 throw new IllegalArgumentException("Argument %d of %s is not assignable to parameter %s" 43 throw new InvalidQueryException("Argument %d of %s is not assignable to parameter %s"
40 .formatted(i, target, parameter)); 44 .formatted(i, target, parameter));
41 } 45 }
42 switch (parameter.getDirection()) { 46 switch (parameter.getDirection()) {
43 case IN -> { 47 case IN -> {
44 if (mutableOutArguments.remove(argument)) { 48 mutableOutArguments.remove(argument);
45 checkInOutUnifiable(argument);
46 }
47 mutableInArguments.add(argument); 49 mutableInArguments.add(argument);
48 } 50 }
49 case OUT -> { 51 case OUT -> {
50 if (mutableInArguments.contains(argument)) { 52 if (!mutableInArguments.contains(argument)) {
51 checkInOutUnifiable(argument); 53 mutableOutArguments.add(argument);
52 } else if (!mutableOutArguments.add(argument)) {
53 checkDuplicateOutUnifiable(argument);
54 } 54 }
55 } 55 }
56 } 56 }
@@ -59,19 +59,6 @@ public abstract class AbstractCallLiteral implements Literal {
59 outArguments = Collections.unmodifiableSet(mutableOutArguments); 59 outArguments = Collections.unmodifiableSet(mutableOutArguments);
60 } 60 }
61 61
62 private static void checkInOutUnifiable(Variable argument) {
63 if (!argument.isUnifiable()) {
64 throw new IllegalArgumentException("Argument %s cannot appear with both %s and %s direction"
65 .formatted(argument, ParameterDirection.IN, ParameterDirection.OUT));
66 }
67 }
68
69 private static void checkDuplicateOutUnifiable(Variable argument) {
70 if (!argument.isUnifiable()) {
71 throw new IllegalArgumentException("Argument %s cannot be bound multiple times".formatted(argument));
72 }
73 }
74
75 public Constraint getTarget() { 62 public Constraint getTarget() {
76 return target; 63 return target;
77 } 64 }
@@ -110,9 +97,18 @@ public abstract class AbstractCallLiteral implements Literal {
110 97
111 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments); 98 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
112 99
100 public AbstractCallLiteral withTarget(Constraint newTarget) {
101 if (Objects.equals(target, newTarget)) {
102 return this;
103 }
104 return withArguments(newTarget, arguments);
105 }
106
107 public abstract AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments);
108
113 @Override 109 @Override
114 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { 110 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
115 if (other == null || getClass() != other.getClass()) { 111 if (!super.equalsWithSubstitution(helper, other)) {
116 return false; 112 return false;
117 } 113 }
118 var otherCallLiteral = (AbstractCallLiteral) other; 114 var otherCallLiteral = (AbstractCallLiteral) other;
@@ -129,15 +125,11 @@ public abstract class AbstractCallLiteral implements Literal {
129 } 125 }
130 126
131 @Override 127 @Override
132 public boolean equals(Object o) { 128 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
133 if (this == o) return true; 129 int result = super.hashCodeWithSubstitution(helper) * 31 + target.hashCode();
134 if (o == null || getClass() != o.getClass()) return false; 130 for (var argument : arguments) {
135 AbstractCallLiteral that = (AbstractCallLiteral) o; 131 result = result * 31 + helper.getVariableHashCode(argument);
136 return target.equals(that.target) && arguments.equals(that.arguments); 132 }
137 } 133 return result;
138
139 @Override
140 public int hashCode() {
141 return Objects.hash(getClass(), target, arguments);
142 } 134 }
143} 135}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java
new file mode 100644
index 00000000..9bb572c0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCountLiteral.java
@@ -0,0 +1,107 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.term.ConstantTerm;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17import java.util.Objects;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public abstract class AbstractCountLiteral<T> extends AbstractCallLiteral {
23 private final Class<T> resultType;
24 private final DataVariable<T> resultVariable;
25
26 protected AbstractCountLiteral(Class<T> resultType, DataVariable<T> resultVariable, Constraint target,
27 List<Variable> arguments) {
28 super(target, arguments);
29 if (!resultVariable.getType().equals(resultType)) {
30 throw new InvalidQueryException("Count result variable %s must be of type %s, got %s instead".formatted(
31 resultVariable, resultType, resultVariable.getType().getName()));
32 }
33 if (arguments.contains(resultVariable)) {
34 throw new InvalidQueryException("Count result variable %s must not appear in the argument list"
35 .formatted(resultVariable));
36 }
37 this.resultType = resultType;
38 this.resultVariable = resultVariable;
39 }
40
41 public Class<T> getResultType() {
42 return resultType;
43 }
44
45 public DataVariable<T> getResultVariable() {
46 return resultVariable;
47 }
48
49 @Override
50 public Set<Variable> getOutputVariables() {
51 return Set.of(resultVariable);
52 }
53
54 protected abstract T zero();
55
56 protected abstract T one();
57
58 @Override
59 public Literal reduce() {
60 var reduction = getTarget().getReduction();
61 return switch (reduction) {
62 case ALWAYS_FALSE -> getResultVariable().assign(new ConstantTerm<>(resultType, zero()));
63 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
64 // its arguments bound as input variables. Thus, there will only be a single match.
65 case ALWAYS_TRUE -> getResultVariable().assign(new ConstantTerm<>(resultType, one()));
66 case NOT_REDUCIBLE -> this;
67 };
68 }
69
70 @Override
71 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
72 if (!super.equalsWithSubstitution(helper, other)) {
73 return false;
74 }
75 var otherCountLiteral = (AbstractCountLiteral<?>) other;
76 return Objects.equals(resultType, otherCountLiteral.resultType) &&
77 helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
78 }
79
80 @Override
81 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
82 return Objects.hash(super.hashCodeWithSubstitution(helper), resultType,
83 helper.getVariableHashCode(resultVariable));
84 }
85
86 protected abstract String operatorName();
87
88 @Override
89 public String toString() {
90 var builder = new StringBuilder();
91 builder.append(resultVariable);
92 builder.append(" is ");
93 builder.append(operatorName());
94 builder.append(' ');
95 builder.append(getTarget().toReferenceString());
96 builder.append('(');
97 var argumentIterator = getArguments().iterator();
98 if (argumentIterator.hasNext()) {
99 builder.append(argumentIterator.next());
100 while (argumentIterator.hasNext()) {
101 builder.append(", ").append(argumentIterator.next());
102 }
103 }
104 builder.append(')');
105 return builder.toString();
106 }
107}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java
new file mode 100644
index 00000000..7d3cabd7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractLiteral.java
@@ -0,0 +1,34 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10
11public abstract class AbstractLiteral implements Literal {
12 @Override
13 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
14 return other != null && getClass() == other.getClass();
15 }
16
17 @Override
18 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
19 return getClass().hashCode();
20 }
21
22 @Override
23 public boolean equals(Object o) {
24 if (this == o) return true;
25 if (o == null || getClass() != o.getClass()) return false;
26 AbstractLiteral that = (AbstractLiteral) o;
27 return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that);
28 }
29
30 @Override
31 public int hashCode() {
32 return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT);
33 }
34}
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
index 3a5eb5c7..e3acfacc 100644
--- 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
@@ -6,7 +6,9 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.*; 13import tools.refinery.store.query.term.*;
12 14
@@ -14,6 +16,8 @@ import java.util.List;
14import java.util.Objects; 16import java.util.Objects;
15import java.util.Set; 17import java.util.Set;
16 18
19// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
20@SuppressWarnings("squid:S2160")
17public class AggregationLiteral<R, T> extends AbstractCallLiteral { 21public class AggregationLiteral<R, T> extends AbstractCallLiteral {
18 private final DataVariable<R> resultVariable; 22 private final DataVariable<R> resultVariable;
19 private final DataVariable<T> inputVariable; 23 private final DataVariable<T> inputVariable;
@@ -23,19 +27,19 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
23 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) { 27 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
24 super(target, arguments); 28 super(target, arguments);
25 if (!inputVariable.getType().equals(aggregator.getInputType())) { 29 if (!inputVariable.getType().equals(aggregator.getInputType())) {
26 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( 30 throw new InvalidQueryException("Input variable %s must of type %s, got %s instead".formatted(
27 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); 31 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
28 } 32 }
29 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) { 33 if (!getArgumentsOfDirection(ParameterDirection.OUT).contains(inputVariable)) {
30 throw new IllegalArgumentException("Input variable %s must be bound with direction %s in the argument list" 34 throw new InvalidQueryException("Input variable %s must be bound with direction %s in the argument list"
31 .formatted(inputVariable, ParameterDirection.OUT)); 35 .formatted(inputVariable, ParameterDirection.OUT));
32 } 36 }
33 if (!resultVariable.getType().equals(aggregator.getResultType())) { 37 if (!resultVariable.getType().equals(aggregator.getResultType())) {
34 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( 38 throw new InvalidQueryException("Result variable %s must of type %s, got %s instead".formatted(
35 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); 39 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
36 } 40 }
37 if (arguments.contains(resultVariable)) { 41 if (arguments.contains(resultVariable)) {
38 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( 42 throw new InvalidQueryException("Result variable %s must not appear in the argument list".formatted(
39 resultVariable)); 43 resultVariable));
40 } 44 }
41 this.resultVariable = resultVariable; 45 this.resultVariable = resultVariable;
@@ -63,7 +67,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
63 @Override 67 @Override
64 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) { 68 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
65 if (positiveVariablesInClause.contains(inputVariable)) { 69 if (positiveVariablesInClause.contains(inputVariable)) {
66 throw new IllegalArgumentException("Aggregation variable %s must not be bound".formatted(inputVariable)); 70 throw new InvalidQueryException("Aggregation variable %s must not be bound".formatted(inputVariable));
67 } 71 }
68 return super.getInputVariables(positiveVariablesInClause); 72 return super.getInputVariables(positiveVariablesInClause);
69 } 73 }
@@ -77,7 +81,7 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
77 yield emptyValue == null ? BooleanLiteral.FALSE : 81 yield emptyValue == null ? BooleanLiteral.FALSE :
78 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue)); 82 resultVariable.assign(new ConstantTerm<>(resultVariable.getType(), emptyValue));
79 } 83 }
80 case ALWAYS_TRUE -> throw new IllegalArgumentException("Trying to aggregate over an infinite set"); 84 case ALWAYS_TRUE -> throw new InvalidQueryException("Trying to aggregate over an infinite set");
81 case NOT_REDUCIBLE -> this; 85 case NOT_REDUCIBLE -> this;
82 }; 86 };
83 } 87 }
@@ -89,6 +93,11 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
89 } 93 }
90 94
91 @Override 95 @Override
96 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
97 return new AggregationLiteral<>(resultVariable, aggregator, inputVariable, newTarget, newArguments);
98 }
99
100 @Override
92 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { 101 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
93 if (!super.equalsWithSubstitution(helper, other)) { 102 if (!super.equalsWithSubstitution(helper, other)) {
94 return false; 103 return false;
@@ -100,18 +109,9 @@ public class AggregationLiteral<R, T> extends AbstractCallLiteral {
100 } 109 }
101 110
102 @Override 111 @Override
103 public boolean equals(Object o) { 112 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
104 if (this == o) return true; 113 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(resultVariable),
105 if (o == null || getClass() != o.getClass()) return false; 114 helper.getVariableHashCode(inputVariable), aggregator);
106 if (!super.equals(o)) return false;
107 AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o;
108 return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) &&
109 aggregator.equals(that.aggregator);
110 }
111
112 @Override
113 public int hashCode() {
114 return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator);
115 } 115 }
116 116
117 @Override 117 @Override
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
index dbf999a2..dadf487f 100644
--- 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
@@ -5,7 +5,9 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.DataVariable; 12import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Term; 13import tools.refinery.store.query.term.Term;
@@ -15,17 +17,32 @@ import java.util.Collections;
15import java.util.Objects; 17import java.util.Objects;
16import java.util.Set; 18import java.util.Set;
17 19
18public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal { 20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19 public AssignLiteral { 21@SuppressWarnings("squid:S2160")
22public class AssignLiteral<T> extends AbstractLiteral {
23 private final DataVariable<T> variable;
24 private final Term<T> term;
25
26 public AssignLiteral(DataVariable<T> variable, Term<T> term) {
20 if (!term.getType().equals(variable.getType())) { 27 if (!term.getType().equals(variable.getType())) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 28 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
22 term, variable.getType().getName(), term.getType().getName())); 29 term, variable.getType().getName(), term.getType().getName()));
23 } 30 }
24 var inputVariables = term.getInputVariables(); 31 var inputVariables = term.getInputVariables();
25 if (inputVariables.contains(variable)) { 32 if (inputVariables.contains(variable)) {
26 throw new IllegalArgumentException("Result variable %s must not appear in the term %s".formatted( 33 throw new InvalidQueryException("Result variable %s must not appear in the term %s".formatted(
27 variable, term)); 34 variable, term));
28 } 35 }
36 this.variable = variable;
37 this.term = term;
38 }
39
40 public DataVariable<T> getVariable() {
41 return variable;
42 }
43
44 public Term<T> getTerm() {
45 return term;
29 } 46 }
30 47
31 @Override 48 @Override
@@ -53,27 +70,19 @@ public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implement
53 if (other == null || getClass() != other.getClass()) { 70 if (other == null || getClass() != other.getClass()) {
54 return false; 71 return false;
55 } 72 }
56 var otherLetLiteral = (AssignLiteral<?>) other; 73 var otherAssignLiteral = (AssignLiteral<?>) other;
57 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, 74 return helper.variableEqual(variable, otherAssignLiteral.variable) &&
58 otherLetLiteral.term); 75 term.equalsWithSubstitution(helper, otherAssignLiteral.term);
59 }
60
61 @Override
62 public String toString() {
63 return "%s is (%s)".formatted(variable, term);
64 } 76 }
65 77
66 @Override 78 @Override
67 public boolean equals(Object obj) { 79 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
68 if (obj == this) return true; 80 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable),
69 if (obj == null || obj.getClass() != this.getClass()) return false; 81 term.hashCodeWithSubstitution(helper));
70 var that = (AssignLiteral<?>) obj;
71 return Objects.equals(this.variable, that.variable) &&
72 Objects.equals(this.term, that.term);
73 } 82 }
74 83
75 @Override 84 @Override
76 public int hashCode() { 85 public String toString() {
77 return Objects.hash(getClass(), variable, term); 86 return "%s is (%s)".formatted(variable, term);
78 } 87 }
79} 88}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
index f312d202..6cd320da 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
11 12
@@ -53,6 +54,11 @@ public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
53 } 54 }
54 55
55 @Override 56 @Override
57 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
58 return hashCode();
59 }
60
61 @Override
56 public String toString() { 62 public String toString() {
57 return Boolean.toString(value); 63 return Boolean.toString(value);
58 } 64 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
index 29772aee..2d0e4e97 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -6,13 +6,17 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
10import tools.refinery.store.query.substitution.Substitution; 12import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.ParameterDirection; 13import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
13 15
14import java.util.*; 16import java.util.*;
15 17
18// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19@SuppressWarnings("squid:S2160")
16public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> { 20public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
17 private final CallPolarity polarity; 21 private final CallPolarity polarity;
18 22
@@ -22,10 +26,14 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
22 int arity = target.arity(); 26 int arity = target.arity();
23 if (polarity.isTransitive()) { 27 if (polarity.isTransitive()) {
24 if (arity != 2) { 28 if (arity != 2) {
25 throw new IllegalArgumentException("Transitive closures can only take binary relations"); 29 throw new InvalidQueryException("Transitive closures can only take binary relations");
26 } 30 }
27 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) { 31 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
28 throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); 32 throw new InvalidQueryException("Transitive closures can only be computed over nodes");
33 }
34 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
35 parameters.get(1).getDirection() != ParameterDirection.OUT) {
36 throw new InvalidQueryException("Transitive closures cannot take input parameters");
29 } 37 }
30 } 38 }
31 this.polarity = polarity; 39 this.polarity = polarity;
@@ -85,22 +93,18 @@ public final class CallLiteral extends AbstractCallLiteral implements CanNegate<
85 } 93 }
86 94
87 @Override 95 @Override
88 public CallLiteral negate() { 96 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
89 return new CallLiteral(polarity.negate(), getTarget(), getArguments()); 97 return Objects.hash(super.hashCodeWithSubstitution(helper), polarity);
90 } 98 }
91 99
92 @Override 100 @Override
93 public boolean equals(Object o) { 101 public CallLiteral negate() {
94 if (this == o) return true; 102 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
95 if (o == null || getClass() != o.getClass()) return false;
96 if (!super.equals(o)) return false;
97 CallLiteral that = (CallLiteral) o;
98 return polarity == that.polarity;
99 } 103 }
100 104
101 @Override 105 @Override
102 public int hashCode() { 106 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
103 return Objects.hash(super.hashCode(), polarity); 107 return new CallLiteral(polarity, newTarget, newArguments);
104 } 108 }
105 109
106 @Override 110 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
index ca70b0fd..716c7109 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
9
8public enum CallPolarity { 10public enum CallPolarity {
9 POSITIVE(true, false), 11 POSITIVE(true, false),
10 NEGATIVE(false, false), 12 NEGATIVE(false, false),
@@ -31,7 +33,7 @@ public enum CallPolarity {
31 return switch (this) { 33 return switch (this) {
32 case POSITIVE -> NEGATIVE; 34 case POSITIVE -> NEGATIVE;
33 case NEGATIVE -> POSITIVE; 35 case NEGATIVE -> POSITIVE;
34 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated"); 36 case TRANSITIVE -> throw new InvalidQueryException("Transitive polarity cannot be negated");
35 }; 37 };
36 } 38 }
37} 39}
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/CheckLiteral.java
index 1ca04c77..dfedd2cb 100644
--- 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/CheckLiteral.java
@@ -5,22 +5,35 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.ConstantTerm; 12import tools.refinery.store.query.term.ConstantTerm;
11import tools.refinery.store.query.term.Term; 13import tools.refinery.store.query.term.Term;
12import tools.refinery.store.query.term.Variable; 14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.term.bool.BoolNotTerm;
16import tools.refinery.store.query.term.bool.BoolTerms;
13 17
14import java.util.Collections; 18import java.util.Collections;
15import java.util.Objects; 19import java.util.Objects;
16import java.util.Set; 20import java.util.Set;
17 21
18public record AssumeLiteral(Term<Boolean> term) implements Literal { 22// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
19 public AssumeLiteral { 23@SuppressWarnings("squid:S2160")
24public class CheckLiteral extends AbstractLiteral implements CanNegate<CheckLiteral> {
25 private final Term<Boolean> term;
26
27 public CheckLiteral(Term<Boolean> term) {
20 if (!term.getType().equals(Boolean.class)) { 28 if (!term.getType().equals(Boolean.class)) {
21 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( 29 throw new InvalidQueryException("Term %s must be of type %s, got %s instead".formatted(
22 term, Boolean.class.getName(), term.getType().getName())); 30 term, Boolean.class.getName(), term.getType().getName()));
23 } 31 }
32 this.term = term;
33 }
34
35 public Term<Boolean> getTerm() {
36 return term;
24 } 37 }
25 38
26 @Override 39 @Override
@@ -38,10 +51,17 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
38 return Set.of(); 51 return Set.of();
39 } 52 }
40 53
41
42 @Override 54 @Override
43 public Literal substitute(Substitution substitution) { 55 public Literal substitute(Substitution substitution) {
44 return new AssumeLiteral(term.substitute(substitution)); 56 return new CheckLiteral(term.substitute(substitution));
57 }
58
59 @Override
60 public CheckLiteral negate() {
61 if (term instanceof BoolNotTerm notTerm) {
62 return new CheckLiteral(notTerm.getBody());
63 }
64 return new CheckLiteral(BoolTerms.not(term));
45 } 65 }
46 66
47 @Override 67 @Override
@@ -49,11 +69,16 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
49 if (other == null || getClass() != other.getClass()) { 69 if (other == null || getClass() != other.getClass()) {
50 return false; 70 return false;
51 } 71 }
52 var otherAssumeLiteral = (AssumeLiteral) other; 72 var otherAssumeLiteral = (CheckLiteral) other;
53 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); 73 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
54 } 74 }
55 75
56 @Override 76 @Override
77 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
78 return Objects.hash(super.hashCodeWithSubstitution(helper), term.hashCodeWithSubstitution(helper));
79 }
80
81 @Override
57 public Literal reduce() { 82 public Literal reduce() {
58 if (term instanceof ConstantTerm<Boolean> constantTerm) { 83 if (term instanceof ConstantTerm<Boolean> constantTerm) {
59 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals. 84 // Return {@link BooleanLiteral#FALSE} for {@code false} or {@code null} literals.
@@ -67,17 +92,4 @@ public record AssumeLiteral(Term<Boolean> term) implements Literal {
67 public String toString() { 92 public String toString() {
68 return "(%s)".formatted(term); 93 return "(%s)".formatted(term);
69 } 94 }
70
71 @Override
72 public boolean equals(Object obj) {
73 if (obj == this) return true;
74 if (obj == null || obj.getClass() != this.getClass()) return false;
75 var that = (AssumeLiteral) obj;
76 return Objects.equals(this.term, that.term);
77 }
78
79 @Override
80 public int hashCode() {
81 return Objects.hash(getClass(), term);
82 }
83} 95}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java
new file mode 100644
index 00000000..a058094d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Connectivity.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import java.util.Locale;
9
10public enum Connectivity {
11 WEAK,
12 STRONG;
13
14 @Override
15 public String toString() {
16 return name().toLowerCase(Locale.ROOT);
17 }
18}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
index 73545620..d83bd584 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable; 11import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
@@ -13,7 +14,24 @@ import tools.refinery.store.query.term.Variable;
13import java.util.Objects; 14import java.util.Objects;
14import java.util.Set; 15import java.util.Set;
15 16
16public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { 17public class ConstantLiteral extends AbstractLiteral {
18 private final NodeVariable variable;
19 private final int nodeId;
20
21 public ConstantLiteral(NodeVariable variable, int nodeId) {
22 this.variable = variable;
23 this.nodeId = nodeId;
24 }
25
26 public NodeVariable getVariable() {
27 return variable;
28 }
29
30 public int getNodeId() {
31 return nodeId;
32 }
33
34
17 @Override 35 @Override
18 public Set<Variable> getOutputVariables() { 36 public Set<Variable> getOutputVariables() {
19 return Set.of(variable); 37 return Set.of(variable);
@@ -43,23 +61,13 @@ public record ConstantLiteral(NodeVariable variable, int nodeId) implements Lite
43 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId; 61 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
44 } 62 }
45 63
46
47 @Override
48 public String toString() {
49 return "%s === @Constant %d".formatted(variable, nodeId);
50 }
51
52 @Override 64 @Override
53 public boolean equals(Object obj) { 65 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
54 if (obj == this) return true; 66 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(variable), nodeId);
55 if (obj == null || obj.getClass() != this.getClass()) return false;
56 var that = (ConstantLiteral) obj;
57 return Objects.equals(this.variable, that.variable) &&
58 this.nodeId == that.nodeId;
59 } 67 }
60 68
61 @Override 69 @Override
62 public int hashCode() { 70 public String toString() {
63 return Objects.hash(getClass(), variable, nodeId); 71 return "%s === @Constant %d".formatted(variable, nodeId);
64 } 72 }
65} 73}
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
index 4d4749c8..3d078d89 100644
--- 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
@@ -6,96 +6,40 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.substitution.Substitution; 9import tools.refinery.store.query.substitution.Substitution;
11import tools.refinery.store.query.term.DataVariable; 10import tools.refinery.store.query.term.DataVariable;
12import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14 12
15import java.util.List; 13import java.util.List;
16import java.util.Objects;
17import java.util.Set;
18
19public class CountLiteral extends AbstractCallLiteral {
20 private final DataVariable<Integer> resultVariable;
21 14
15public class CountLiteral extends AbstractCountLiteral<Integer> {
22 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) { 16 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
23 super(target, arguments); 17 super(Integer.class, resultVariable, target, arguments);
24 if (!resultVariable.getType().equals(Integer.class)) {
25 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted(
26 resultVariable, Integer.class.getName(), resultVariable.getType().getName()));
27 }
28 if (arguments.contains(resultVariable)) {
29 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list"
30 .formatted(resultVariable));
31 }
32 this.resultVariable = resultVariable;
33 }
34
35 public DataVariable<Integer> getResultVariable() {
36 return resultVariable;
37 } 18 }
38 19
39 @Override 20 @Override
40 public Set<Variable> getOutputVariables() { 21 protected Integer zero() {
41 return Set.of(resultVariable); 22 return 0;
42 } 23 }
43 24
44 @Override 25 @Override
45 public Literal reduce() { 26 protected Integer one() {
46 var reduction = getTarget().getReduction(); 27 return 1;
47 return switch (reduction) {
48 case ALWAYS_FALSE -> getResultVariable().assign(IntTerms.constant(0));
49 // The only way a constant {@code true} predicate can be called in a negative position is to have all of
50 // its arguments bound as input variables. Thus, there will only be a single match.
51 case ALWAYS_TRUE -> getResultVariable().assign(IntTerms.constant(1));
52 case NOT_REDUCIBLE -> this;
53 };
54 } 28 }
55 29
56 @Override 30 @Override
57 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { 31 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
58 return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments); 32 return new CountLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
59 } 33 substitutedArguments);
60
61 @Override
62 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
63 if (!super.equalsWithSubstitution(helper, other)) {
64 return false;
65 }
66 var otherCountLiteral = (CountLiteral) other;
67 return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
68 }
69
70 @Override
71 public boolean equals(Object o) {
72 if (this == o) return true;
73 if (o == null || getClass() != o.getClass()) return false;
74 if (!super.equals(o)) return false;
75 CountLiteral that = (CountLiteral) o;
76 return resultVariable.equals(that.resultVariable);
77 } 34 }
78 35
79 @Override 36 @Override
80 public int hashCode() { 37 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
81 return Objects.hash(super.hashCode(), resultVariable); 38 return new CountLiteral(getResultVariable(), newTarget, newArguments);
82 } 39 }
83 40
84 @Override 41 @Override
85 public String toString() { 42 protected String operatorName() {
86 var builder = new StringBuilder(); 43 return "count";
87 builder.append(resultVariable);
88 builder.append(" is count ");
89 builder.append(getTarget().toReferenceString());
90 builder.append("(");
91 var argumentIterator = getArguments().iterator();
92 if (argumentIterator.hasNext()) {
93 builder.append(argumentIterator.next());
94 while (argumentIterator.hasNext()) {
95 builder.append(", ").append(argumentIterator.next());
96 }
97 }
98 builder.append(")");
99 return builder.toString();
100 } 44 }
101} 45}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
index 28ba7625..7343f709 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -5,16 +5,44 @@
5 */ 5 */
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.NodeVariable;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
12 13
13import java.util.Objects; 14import java.util.Objects;
14import java.util.Set; 15import java.util.Set;
15 16
16public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) 17// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
17 implements CanNegate<EquivalenceLiteral> { 18@SuppressWarnings("squid:S2160")
19public final class EquivalenceLiteral extends AbstractLiteral implements CanNegate<EquivalenceLiteral> {
20 private final boolean positive;
21 private final Variable left;
22 private final Variable right;
23
24 public EquivalenceLiteral(boolean positive, Variable left, Variable right) {
25 if (!left.tryGetType().equals(right.tryGetType())) {
26 throw new InvalidQueryException("Variables %s and %s of different type cannot be equivalent"
27 .formatted(left, right));
28 }
29 this.positive = positive;
30 this.left = left;
31 this.right = right;
32 }
33
34 public boolean isPositive() {
35 return positive;
36 }
37
38 public Variable getLeft() {
39 return left;
40 }
41
42 public Variable getRight() {
43 return right;
44 }
45
18 @Override 46 @Override
19 public Set<Variable> getOutputVariables() { 47 public Set<Variable> getOutputVariables() {
20 return Set.of(left); 48 return Set.of(left);
@@ -37,8 +65,8 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab
37 65
38 @Override 66 @Override
39 public EquivalenceLiteral substitute(Substitution substitution) { 67 public EquivalenceLiteral substitute(Substitution substitution) {
40 return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left), 68 return new EquivalenceLiteral(positive, substitution.getSubstitute(left),
41 substitution.getTypeSafeSubstitute(right)); 69 substitution.getSubstitute(right));
42 } 70 }
43 71
44 @Override 72 @Override
@@ -55,27 +83,18 @@ public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariab
55 return false; 83 return false;
56 } 84 }
57 var otherEquivalenceLiteral = (EquivalenceLiteral) other; 85 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
58 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right, 86 return helper.variableEqual(left, otherEquivalenceLiteral.left) &&
59 otherEquivalenceLiteral.right); 87 helper.variableEqual(right, otherEquivalenceLiteral.right);
60 }
61
62 @Override
63 public String toString() {
64 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
65 } 88 }
66 89
67 @Override 90 @Override
68 public boolean equals(Object obj) { 91 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
69 if (obj == this) return true; 92 return Objects.hash(super.hashCodeWithSubstitution(helper), helper.getVariableHashCode(left),
70 if (obj == null || obj.getClass() != this.getClass()) return false; 93 helper.getVariableHashCode(right));
71 var that = (EquivalenceLiteral) obj;
72 return this.positive == that.positive &&
73 Objects.equals(this.left, that.left) &&
74 Objects.equals(this.right, that.right);
75 } 94 }
76 95
77 @Override 96 @Override
78 public int hashCode() { 97 public String toString() {
79 return Objects.hash(getClass(), positive, left, right); 98 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
80 } 99 }
81} 100}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
index ce6c11fe..cb16ab00 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.literal; 6package tools.refinery.store.query.literal;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.term.Variable; 11import tools.refinery.store.query.term.Variable;
11 12
@@ -26,4 +27,6 @@ public interface Literal {
26 27
27 @SuppressWarnings("BooleanMethodIsAlwaysInverted") 28 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
28 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other); 29 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
30
31 int hashCodeWithSubstitution(LiteralHashCodeHelper helper);
29} 32}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
index b3a87811..6056da45 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -16,7 +16,7 @@ public final class Literals {
16 return literal.negate(); 16 return literal.negate();
17 } 17 }
18 18
19 public static AssumeLiteral assume(Term<Boolean> term) { 19 public static CheckLiteral check(Term<Boolean> term) {
20 return new AssumeLiteral(term); 20 return new CheckLiteral(term);
21 } 21 }
22} 22}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java
new file mode 100644
index 00000000..f7323947
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RepresentativeElectionLiteral.java
@@ -0,0 +1,119 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.NodeVariable;
14import tools.refinery.store.query.term.ParameterDirection;
15import tools.refinery.store.query.term.Variable;
16
17import java.util.List;
18import java.util.Set;
19
20// {@link Object#equals(Object)} is implemented by {@link AbstractLiteral}.
21@SuppressWarnings("squid:S2160")
22public class RepresentativeElectionLiteral extends AbstractCallLiteral {
23 private final Connectivity connectivity;
24
25 public RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, NodeVariable specific,
26 NodeVariable representative) {
27 this(connectivity, target, List.of(specific, representative));
28 }
29
30 private RepresentativeElectionLiteral(Connectivity connectivity, Constraint target, List<Variable> arguments) {
31 super(target, arguments);
32 this.connectivity = connectivity;
33 var parameters = target.getParameters();
34 int arity = target.arity();
35 if (arity != 2) {
36 throw new InvalidQueryException("SCCs can only take binary relations");
37 }
38 if (parameters.get(0).isDataVariable() || parameters.get(1).isDataVariable()) {
39 throw new InvalidQueryException("SCCs can only be computed over nodes");
40 }
41 if (parameters.get(0).getDirection() != ParameterDirection.OUT ||
42 parameters.get(1).getDirection() != ParameterDirection.OUT) {
43 throw new InvalidQueryException("SCCs cannot take input parameters");
44 }
45 }
46
47 public Connectivity getConnectivity() {
48 return connectivity;
49 }
50
51 @Override
52 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
53 return new RepresentativeElectionLiteral(connectivity, getTarget(), substitutedArguments);
54 }
55
56 @Override
57 public Set<Variable> getOutputVariables() {
58 return getArgumentsOfDirection(ParameterDirection.OUT);
59 }
60
61 @Override
62 public Set<Variable> getInputVariables(Set<? extends Variable> positiveVariablesInClause) {
63 return Set.of();
64 }
65
66 @Override
67 public Set<Variable> getPrivateVariables(Set<? extends Variable> positiveVariablesInClause) {
68 return Set.of();
69 }
70
71 @Override
72 public Literal reduce() {
73 var reduction = getTarget().getReduction();
74 return switch (reduction) {
75 case ALWAYS_FALSE -> BooleanLiteral.FALSE;
76 case ALWAYS_TRUE -> throw new InvalidQueryException(
77 "Trying to elect representatives over an infinite set");
78 case NOT_REDUCIBLE -> this;
79 };
80 }
81
82 @Override
83 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
84 return new RepresentativeElectionLiteral(connectivity, newTarget, newArguments);
85 }
86
87 @Override
88 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
89 if (!super.equalsWithSubstitution(helper, other)) {
90 return false;
91 }
92 var otherRepresentativeElectionLiteral = (RepresentativeElectionLiteral) other;
93 return connectivity.equals(otherRepresentativeElectionLiteral.connectivity);
94 }
95
96 @Override
97 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
98 return super.hashCodeWithSubstitution(helper) * 31 + connectivity.hashCode();
99 }
100
101 @Override
102 public String toString() {
103 var builder = new StringBuilder();
104 builder.append("@Representative(\"");
105 builder.append(connectivity);
106 builder.append("\") ");
107 builder.append(getTarget().toReferenceString());
108 builder.append("(");
109 var argumentIterator = getArguments().iterator();
110 if (argumentIterator.hasNext()) {
111 builder.append(argumentIterator.next());
112 while (argumentIterator.hasNext()) {
113 builder.append(", ").append(argumentIterator.next());
114 }
115 }
116 builder.append(")");
117 return builder.toString();
118 }
119}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
index a710c64d..dcfe6cc5 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AbstractResultSet.java
@@ -28,7 +28,7 @@ public abstract class AbstractResultSet<T> implements ResultSet<T> {
28 } 28 }
29 29
30 @Override 30 @Override
31 public Query<T> getQuery() { 31 public Query<T> getCanonicalQuery() {
32 return query; 32 return query;
33 } 33 }
34 34
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
index 02809477..5b75b103 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/AnyResultSet.java
@@ -11,7 +11,7 @@ import tools.refinery.store.query.dnf.AnyQuery;
11public sealed interface AnyResultSet permits ResultSet { 11public sealed interface AnyResultSet permits ResultSet {
12 ModelQueryAdapter getAdapter(); 12 ModelQueryAdapter getAdapter();
13 13
14 AnyQuery getQuery(); 14 AnyQuery getCanonicalQuery();
15 15
16 int size(); 16 int size();
17} 17}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
index 2795a44b..991b1e32 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/EmptyResultSet.java
@@ -18,7 +18,7 @@ public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) imple
18 } 18 }
19 19
20 @Override 20 @Override
21 public Query<T> getQuery() { 21 public Query<T> getCanonicalQuery() {
22 return query; 22 return query;
23 } 23 }
24 24
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
index 39006d65..df12b967 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/OrderedResultSet.java
@@ -17,7 +17,7 @@ public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> {
17 private final ResultSet<T> resultSet; 17 private final ResultSet<T> resultSet;
18 private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>(); 18 private final OrderStatisticTree<Tuple> tree = new OrderStatisticTree<>();
19 private final ResultSetListener<T> listener = (key, fromValue, toValue) -> { 19 private final ResultSetListener<T> listener = (key, fromValue, toValue) -> {
20 var defaultValue = getQuery().defaultValue(); 20 var defaultValue = getCanonicalQuery().defaultValue();
21 if (Objects.equals(defaultValue, toValue)) { 21 if (Objects.equals(defaultValue, toValue)) {
22 tree.remove(key); 22 tree.remove(key);
23 } else { 23 } else {
@@ -45,8 +45,8 @@ public class OrderedResultSet<T> implements AutoCloseable, ResultSet<T> {
45 } 45 }
46 46
47 @Override 47 @Override
48 public Query<T> getQuery() { 48 public Query<T> getCanonicalQuery() {
49 return resultSet.getQuery(); 49 return resultSet.getCanonicalQuery();
50 } 50 }
51 51
52 @Override 52 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
index 33d1ea95..a6e99784 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/resultset/ResultSet.java
@@ -10,7 +10,7 @@ import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.tuple.Tuple; 10import tools.refinery.store.tuple.Tuple;
11 11
12public non-sealed interface ResultSet<T> extends AnyResultSet { 12public non-sealed interface ResultSet<T> extends AnyResultSet {
13 Query<T> getQuery(); 13 Query<T> getCanonicalQuery();
14 14
15 T get(Tuple parameters); 15 T get(Tuple parameters);
16 16
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java
new file mode 100644
index 00000000..fb4c14a7
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/AbstractRecursiveRewriter.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.util.CycleDetectingMapper;
11
12public abstract class AbstractRecursiveRewriter implements DnfRewriter {
13 private final CycleDetectingMapper<Dnf, Dnf> mapper = new CycleDetectingMapper<>(Dnf::name, this::map);
14
15 @Override
16 public Dnf rewrite(Dnf dnf) {
17 return mapper.map(dnf);
18 }
19
20 protected Dnf map(Dnf dnf) {
21 var result = doRewrite(dnf);
22 return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, result) ? dnf : result;
23 }
24
25 protected abstract Dnf doRewrite(Dnf dnf);
26}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java
new file mode 100644
index 00000000..aa06a05a
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/ClauseInputParameterResolver.java
@@ -0,0 +1,160 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.literal.*;
11import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14
15import java.util.*;
16
17class ClauseInputParameterResolver {
18 private final InputParameterResolver rewriter;
19 private final String dnfName;
20 private final int clauseIndex;
21 private final Set<Variable> positiveVariables = new LinkedHashSet<>();
22 private final List<Literal> inlinedLiterals = new ArrayList<>();
23 private final Deque<Literal> workList;
24 private int helperIndex = 0;
25
26 public ClauseInputParameterResolver(InputParameterResolver rewriter, List<Literal> context, DnfClause clause,
27 String dnfName, int clauseIndex) {
28 this.rewriter = rewriter;
29 this.dnfName = dnfName;
30 this.clauseIndex = clauseIndex;
31 workList = new ArrayDeque<>(clause.literals().size() + context.size());
32 for (var literal : context) {
33 workList.addLast(literal);
34 }
35 for (var literal : clause.literals()) {
36 workList.addLast(literal);
37 }
38 }
39
40 public List<Literal> rewriteClause() {
41 while (!workList.isEmpty()) {
42 var literal = workList.removeFirst();
43 processLiteral(literal);
44 }
45 return inlinedLiterals;
46 }
47
48 private void processLiteral(Literal literal) {
49 if (!(literal instanceof AbstractCallLiteral abstractCallLiteral) ||
50 !(abstractCallLiteral.getTarget() instanceof Dnf targetDnf)) {
51 markAsDone(literal);
52 return;
53 }
54 boolean hasInputParameter = hasInputParameter(targetDnf);
55 if (!hasInputParameter) {
56 targetDnf = rewriter.rewrite(targetDnf);
57 }
58 if (inlinePositiveClause(abstractCallLiteral, targetDnf)) {
59 return;
60 }
61 if (eliminateDoubleNegation(abstractCallLiteral, targetDnf)) {
62 return;
63 }
64 if (hasInputParameter) {
65 rewriteWithCurrentContext(abstractCallLiteral, targetDnf);
66 return;
67 }
68 markAsDone(abstractCallLiteral.withTarget(targetDnf));
69 }
70
71 private void markAsDone(Literal literal) {
72 positiveVariables.addAll(literal.getOutputVariables());
73 inlinedLiterals.add(literal);
74 }
75
76 private boolean inlinePositiveClause(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
77 var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.POSITIVE);
78 if (targetLiteral == null) {
79 return false;
80 }
81 var substitution = asSubstitution(abstractCallLiteral, targetDnf);
82 var substitutedLiteral = targetLiteral.substitute(substitution);
83 workList.addFirst(substitutedLiteral);
84 return true;
85 }
86
87 private boolean eliminateDoubleNegation(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
88 var targetLiteral = getSingleLiteral(abstractCallLiteral, targetDnf, CallPolarity.NEGATIVE);
89 if (!(targetLiteral instanceof CallLiteral targetCallLiteral) ||
90 targetCallLiteral.getPolarity() != CallPolarity.NEGATIVE) {
91 return false;
92 }
93 var substitution = asSubstitution(abstractCallLiteral, targetDnf);
94 var substitutedLiteral = (CallLiteral) targetCallLiteral.substitute(substitution);
95 workList.addFirst(substitutedLiteral.negate());
96 return true;
97 }
98
99 private void rewriteWithCurrentContext(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf) {
100 var contextBuilder = Dnf.builder("%s#clause%d#helper%d".formatted(dnfName, clauseIndex, helperIndex));
101 helperIndex++;
102 contextBuilder.parameters(positiveVariables, ParameterDirection.OUT);
103 contextBuilder.clause(inlinedLiterals);
104 var contextDnf = contextBuilder.build();
105 var contextCall = new CallLiteral(CallPolarity.POSITIVE, contextDnf, List.copyOf(positiveVariables));
106 inlinedLiterals.clear();
107 var substitution = Substitution.builder().renewing().build();
108 var context = new ArrayList<Literal>();
109 context.add(contextCall.substitute(substitution));
110 int arity = targetDnf.arity();
111 for (int i = 0; i < arity; i++) {
112 var parameter = targetDnf.getSymbolicParameters().get(i).getVariable();
113 var argument = abstractCallLiteral.getArguments().get(i);
114 context.add(new EquivalenceLiteral(true, parameter, substitution.getSubstitute(argument)));
115 }
116 var rewrittenDnf = rewriter.rewriteWithContext(context, targetDnf);
117 workList.addFirst(abstractCallLiteral.withTarget(rewrittenDnf));
118 workList.addFirst(contextCall);
119 }
120
121 private static boolean hasInputParameter(Dnf targetDnf) {
122 for (var parameter : targetDnf.getParameters()) {
123 if (parameter.getDirection() != ParameterDirection.OUT) {
124 return true;
125 }
126 }
127 return false;
128 }
129
130 private static Literal getSingleLiteral(AbstractCallLiteral abstractCallLiteral, Dnf targetDnf,
131 CallPolarity polarity) {
132 if (!(abstractCallLiteral instanceof CallLiteral callLiteral) ||
133 callLiteral.getPolarity() != polarity) {
134 return null;
135 }
136 var clauses = targetDnf.getClauses();
137 if (clauses.size() != 1) {
138 return null;
139 }
140 var targetLiterals = clauses.get(0).literals();
141 if (targetLiterals.size() != 1) {
142 return null;
143 }
144 return targetLiterals.get(0);
145 }
146
147 private static Substitution asSubstitution(AbstractCallLiteral callLiteral, Dnf targetDnf) {
148 var builder = Substitution.builder().renewing();
149 var arguments = callLiteral.getArguments();
150 var parameters = targetDnf.getSymbolicParameters();
151 int arity = arguments.size();
152 if (parameters.size() != arity) {
153 throw new IllegalArgumentException("Call %s of %s arity mismatch".formatted(callLiteral, targetDnf));
154 }
155 for (int i = 0; i < arity; i++) {
156 builder.putChecked(parameters.get(i).getVariable(), arguments.get(i));
157 }
158 return builder.build();
159 }
160}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java
new file mode 100644
index 00000000..5b4f65e5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/CompositeRewriter.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9
10import java.util.ArrayList;
11import java.util.List;
12
13public class CompositeRewriter implements DnfRewriter {
14 private final List<DnfRewriter> rewriterList = new ArrayList<>();
15
16 public void addFirst(DnfRewriter rewriter) {
17 rewriterList.add(rewriter);
18 }
19
20 @Override
21 public Dnf rewrite(Dnf dnf) {
22 Dnf rewrittenDnf = dnf;
23 for (int i = rewriterList.size() - 1; i >= 0; i--) {
24 var rewriter = rewriterList.get(i);
25 rewrittenDnf = rewriter.rewrite(rewrittenDnf);
26 }
27 return rewrittenDnf;
28 }
29}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java
new file mode 100644
index 00000000..5d8359d1
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DnfRewriter.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.Query;
11
12@FunctionalInterface
13public interface DnfRewriter {
14 Dnf rewrite(Dnf dnf);
15
16 default AnyQuery rewrite(AnyQuery query) {
17 return rewrite((Query<?>) query);
18 }
19
20 default <T> Query<T> rewrite(Query<T> query) {
21 var rewrittenDnf = rewrite(query.getDnf());
22 return query.withDnf(rewrittenDnf);
23 }
24}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java
new file mode 100644
index 00000000..0c786470
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/DuplicateDnfRemover.java
@@ -0,0 +1,98 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfClause;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.equality.DnfEqualityChecker;
12import tools.refinery.store.query.literal.AbstractCallLiteral;
13import tools.refinery.store.query.literal.Literal;
14
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20public class DuplicateDnfRemover extends AbstractRecursiveRewriter {
21 private final Map<CanonicalDnf, Dnf> dnfCache = new HashMap<>();
22 private final Map<Dnf, Query<?>> queryCache = new HashMap<>();
23
24 @Override
25 protected Dnf map(Dnf dnf) {
26 var result = super.map(dnf);
27 return dnfCache.computeIfAbsent(new CanonicalDnf(result), CanonicalDnf::getDnf);
28 }
29
30 @Override
31 protected Dnf doRewrite(Dnf dnf) {
32 var builder = Dnf.builderFrom(dnf);
33 for (var clause : dnf.getClauses()) {
34 builder.clause(rewriteClause(clause));
35 }
36 return builder.build();
37 }
38
39 private List<Literal> rewriteClause(DnfClause clause) {
40 var originalLiterals = clause.literals();
41 var literals = new ArrayList<Literal>(originalLiterals.size());
42 for (var literal : originalLiterals) {
43 var rewrittenLiteral = literal;
44 if (literal instanceof AbstractCallLiteral abstractCallLiteral &&
45 abstractCallLiteral.getTarget() instanceof Dnf targetDnf) {
46 var rewrittenTarget = rewrite(targetDnf);
47 rewrittenLiteral = abstractCallLiteral.withTarget(rewrittenTarget);
48 }
49 literals.add(rewrittenLiteral);
50 }
51 return literals;
52 }
53
54 @Override
55 public <T> Query<T> rewrite(Query<T> query) {
56 var rewrittenDnf = rewrite(query.getDnf());
57 // {@code withDnf} will always return the appropriate type.
58 @SuppressWarnings("unchecked")
59 var rewrittenQuery = (Query<T>) queryCache.computeIfAbsent(rewrittenDnf, query::withDnf);
60 return rewrittenQuery;
61 }
62
63 private static class CanonicalDnf {
64 private final Dnf dnf;
65 private final int hash;
66
67 public CanonicalDnf(Dnf dnf) {
68 this.dnf = dnf;
69 hash = dnf.hashCodeWithSubstitution();
70 }
71
72 public Dnf getDnf() {
73 return dnf;
74 }
75
76 @Override
77 public boolean equals(Object obj) {
78 if (this == obj) {
79 return true;
80 }
81 if (obj == null || getClass() != obj.getClass()) {
82 return false;
83 }
84 var otherCanonicalDnf = (CanonicalDnf) obj;
85 return dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, otherCanonicalDnf.dnf);
86 }
87
88 @Override
89 public int hashCode() {
90 return hash;
91 }
92
93 @Override
94 public String toString() {
95 return dnf.name();
96 }
97 }
98}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java
new file mode 100644
index 00000000..cd8a2e7d
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/rewriter/InputParameterResolver.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.dnf.DnfBuilder;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.ParameterDirection;
12import tools.refinery.store.query.term.Variable;
13
14import java.util.HashSet;
15import java.util.List;
16
17public class InputParameterResolver extends AbstractRecursiveRewriter {
18 @Override
19 protected Dnf doRewrite(Dnf dnf) {
20 return rewriteWithContext(List.of(), dnf);
21 }
22
23 Dnf rewriteWithContext(List<Literal> context, Dnf dnf) {
24 var dnfName = dnf.name();
25 var builder = Dnf.builder(dnfName);
26 createSymbolicParameters(context, dnf, builder);
27 builder.functionalDependencies(dnf.getFunctionalDependencies());
28 var clauses = dnf.getClauses();
29 int clauseCount = clauses.size();
30 for (int i = 0; i < clauseCount; i++) {
31 var clause = clauses.get(i);
32 var clauseRewriter = new ClauseInputParameterResolver(this, context, clause, dnfName, i);
33 builder.clause(clauseRewriter.rewriteClause());
34 }
35 return builder.build();
36 }
37
38 private static void createSymbolicParameters(List<Literal> context, Dnf dnf, DnfBuilder builder) {
39 var positiveInContext = new HashSet<Variable>();
40 for (var literal : context) {
41 positiveInContext.addAll(literal.getOutputVariables());
42 }
43 for (var symbolicParameter : dnf.getSymbolicParameters()) {
44 var variable = symbolicParameter.getVariable();
45 var isOutput = symbolicParameter.getDirection() == ParameterDirection.OUT ||
46 positiveInContext.contains(variable);
47 var direction = isOutput ? ParameterDirection.OUT : ParameterDirection.IN;
48 builder.parameter(variable, direction);
49 }
50 }
51}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
index d0ae3c12..5cecc35b 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AbstractTerm.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9 10
10import java.util.Objects; 11import java.util.Objects;
11 12
@@ -17,13 +18,18 @@ public abstract class AbstractTerm<T> implements Term<T> {
17 } 18 }
18 19
19 @Override 20 @Override
21 public Class<T> getType() {
22 return type;
23 }
24
25 @Override
20 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { 26 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
21 return getClass().equals(other.getClass()) && type.equals(other.getType()); 27 return other != null && getClass() == other.getClass() && type.equals(other.getType());
22 } 28 }
23 29
24 @Override 30 @Override
25 public Class<T> getType() { 31 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
26 return type; 32 return Objects.hash(getClass(), type);
27 } 33 }
28 34
29 @Override 35 @Override
@@ -31,11 +37,11 @@ public abstract class AbstractTerm<T> implements Term<T> {
31 if (this == o) return true; 37 if (this == o) return true;
32 if (o == null || getClass() != o.getClass()) return false; 38 if (o == null || getClass() != o.getClass()) return false;
33 AbstractTerm<?> that = (AbstractTerm<?>) o; 39 AbstractTerm<?> that = (AbstractTerm<?>) o;
34 return type.equals(that.type); 40 return equalsWithSubstitution(LiteralEqualityHelper.DEFAULT, that);
35 } 41 }
36 42
37 @Override 43 @Override
38 public int hashCode() { 44 public int hashCode() {
39 return Objects.hash(getClass(), type); 45 return hashCodeWithSubstitution(LiteralHashCodeHelper.DEFAULT);
40 } 46 }
41} 47}
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
index 192c39c5..3801bc11 100644
--- 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10 11
11import java.util.Optional; 12import java.util.Optional;
@@ -22,8 +23,18 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm
22 } 23 }
23 24
24 @Override 25 @Override
26 public boolean isNodeVariable() {
27 return false;
28 }
29
30 @Override
31 public boolean isDataVariable() {
32 return true;
33 }
34
35 @Override
25 public NodeVariable asNodeVariable() { 36 public NodeVariable asNodeVariable() {
26 throw new IllegalStateException("%s is a data variable".formatted(this)); 37 throw new InvalidQueryException("%s is a data variable".formatted(this));
27 } 38 }
28 39
29 @Override 40 @Override
@@ -37,11 +48,6 @@ public abstract sealed class AnyDataVariable extends Variable implements AnyTerm
37 } 48 }
38 49
39 @Override 50 @Override
40 public boolean isUnifiable() {
41 return false;
42 }
43
44 @Override
45 public abstract AnyDataVariable renew(@Nullable String name); 51 public abstract AnyDataVariable renew(@Nullable String name);
46 52
47 @Override 53 @Override
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
index c12c0166..f136b68d 100644
--- 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 8import tools.refinery.store.query.equality.LiteralEqualityHelper;
9import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 10import tools.refinery.store.query.substitution.Substitution;
10 11
11import java.util.Set; 12import java.util.Set;
@@ -17,5 +18,7 @@ public sealed interface AnyTerm permits AnyDataVariable, Term {
17 18
18 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); 19 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
19 20
21 int hashCodeWithSubstitution(LiteralHashCodeHelper helper);
22
20 Set<AnyDataVariable> getInputVariables(); 23 Set<AnyDataVariable> getInputVariables();
21} 24}
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
index 8ad17839..cdbf592a 100644
--- 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
@@ -5,7 +5,9 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 12import tools.refinery.store.query.valuation.Valuation;
11 13
@@ -14,6 +16,8 @@ import java.util.HashSet;
14import java.util.Objects; 16import java.util.Objects;
15import java.util.Set; 17import java.util.Set;
16 18
19// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
20@SuppressWarnings("squid:S2160")
17public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> { 21public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
18 private final Class<T1> leftType; 22 private final Class<T1> leftType;
19 private final Class<T2> rightType; 23 private final Class<T2> rightType;
@@ -23,11 +27,11 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
23 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) { 27 protected BinaryTerm(Class<R> type, Class<T1> leftType, Class<T2> rightType, Term<T1> left, Term<T2> right) {
24 super(type); 28 super(type);
25 if (!left.getType().equals(leftType)) { 29 if (!left.getType().equals(leftType)) {
26 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted( 30 throw new InvalidQueryException("Expected left %s to be of type %s, got %s instead".formatted(
27 left, leftType.getName(), left.getType().getName())); 31 left, leftType.getName(), left.getType().getName()));
28 } 32 }
29 if (!right.getType().equals(rightType)) { 33 if (!right.getType().equals(rightType)) {
30 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted( 34 throw new InvalidQueryException("Expected right %s to be of type %s, got %s instead".formatted(
31 right, rightType.getName(), right.getType().getName())); 35 right, rightType.getName(), right.getType().getName()));
32 } 36 }
33 this.leftType = leftType; 37 this.leftType = leftType;
@@ -80,6 +84,12 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
80 } 84 }
81 85
82 @Override 86 @Override
87 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
88 return Objects.hash(super.hashCodeWithSubstitution(helper), leftType.hashCode(), rightType.hashCode(),
89 left.hashCodeWithSubstitution(helper), right.hashCodeWithSubstitution(helper));
90 }
91
92 @Override
83 public Term<R> substitute(Substitution substitution) { 93 public Term<R> substitute(Substitution substitution) {
84 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); 94 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
85 } 95 }
@@ -93,21 +103,4 @@ public abstract class BinaryTerm<R, T1, T2> extends AbstractTerm<R> {
93 inputVariables.addAll(right.getInputVariables()); 103 inputVariables.addAll(right.getInputVariables());
94 return Collections.unmodifiableSet(inputVariables); 104 return Collections.unmodifiableSet(inputVariables);
95 } 105 }
96
97 @Override
98 public boolean equals(Object o) {
99 if (this == o) return true;
100 if (o == null || getClass() != o.getClass()) return false;
101 if (!super.equals(o)) return false;
102 BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o;
103 return Objects.equals(leftType, that.leftType) &&
104 Objects.equals(rightType, that.rightType) &&
105 Objects.equals(left, that.left) &&
106 Objects.equals(right, that.right);
107 }
108
109 @Override
110 public int hashCode() {
111 return Objects.hash(super.hashCode(), leftType, rightType, left, right);
112 }
113} 106}
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
index 2f6c56d1..415ae286 100644
--- 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
@@ -5,20 +5,24 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 12import tools.refinery.store.query.valuation.Valuation;
11 13
12import java.util.Objects; 14import java.util.Objects;
13import java.util.Set; 15import java.util.Set;
14 16
17// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
18@SuppressWarnings("squid:S2160")
15public final class ConstantTerm<T> extends AbstractTerm<T> { 19public final class ConstantTerm<T> extends AbstractTerm<T> {
16 private final T value; 20 private final T value;
17 21
18 public ConstantTerm(Class<T> type, T value) { 22 public ConstantTerm(Class<T> type, T value) {
19 super(type); 23 super(type);
20 if (value != null && !type.isInstance(value)) { 24 if (value != null && !type.isInstance(value)) {
21 throw new IllegalArgumentException("Value %s is not an instance of %s".formatted(value, type.getName())); 25 throw new InvalidQueryException("Value %s is not an instance of %s".formatted(value, type.getName()));
22 } 26 }
23 this.value = value; 27 this.value = value;
24 } 28 }
@@ -47,6 +51,11 @@ public final class ConstantTerm<T> extends AbstractTerm<T> {
47 } 51 }
48 52
49 @Override 53 @Override
54 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
55 return Objects.hash(super.hashCodeWithSubstitution(helper), Objects.hash(value));
56 }
57
58 @Override
50 public Set<AnyDataVariable> getInputVariables() { 59 public Set<AnyDataVariable> getInputVariables() {
51 return Set.of(); 60 return Set.of();
52 } 61 }
@@ -55,17 +64,4 @@ public final class ConstantTerm<T> extends AbstractTerm<T> {
55 public String toString() { 64 public String toString() {
56 return value.toString(); 65 return value.toString();
57 } 66 }
58
59 @Override
60 public boolean equals(Object o) {
61 if (this == o) return true;
62 if (o == null || getClass() != o.getClass()) return false;
63 ConstantTerm<?> that = (ConstantTerm<?>) o;
64 return Objects.equals(value, that.value);
65 }
66
67 @Override
68 public int hashCode() {
69 return Objects.hash(value);
70 }
71} 67}
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
index 00950360..2206b522 100644
--- 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
@@ -6,7 +6,10 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
11import tools.refinery.store.query.equality.LiteralHashCodeHelper;
12import tools.refinery.store.query.literal.EquivalenceLiteral;
10import tools.refinery.store.query.literal.Literal; 13import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.substitution.Substitution; 14import tools.refinery.store.query.substitution.Substitution;
12import tools.refinery.store.query.valuation.Valuation; 15import tools.refinery.store.query.valuation.Valuation;
@@ -39,8 +42,8 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
39 @Override 42 @Override
40 public <U> DataVariable<U> asDataVariable(Class<U> newType) { 43 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
41 if (!getType().equals(newType)) { 44 if (!getType().equals(newType)) {
42 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(), 45 throw new InvalidQueryException("%s is not of type %s but of type %s"
43 getType().getName())); 46 .formatted(this, newType.getName(), getType().getName()));
44 } 47 }
45 @SuppressWarnings("unchecked") 48 @SuppressWarnings("unchecked")
46 var result = (DataVariable<U>) this; 49 var result = (DataVariable<U>) this;
@@ -62,6 +65,16 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
62 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable); 65 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
63 } 66 }
64 67
68 @Override
69 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
70 return helper.getVariableHashCode(this);
71 }
72
73 @Override
74 public int hashCodeWithSubstitution(int sequenceNumber) {
75 return Objects.hash(type, sequenceNumber);
76 }
77
65 public Literal assign(AssignedValue<T> value) { 78 public Literal assign(AssignedValue<T> value) {
66 return value.toLiteral(this); 79 return value.toLiteral(this);
67 } 80 }
@@ -79,4 +92,12 @@ public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
79 public int hashCode() { 92 public int hashCode() {
80 return Objects.hash(super.hashCode(), type); 93 return Objects.hash(super.hashCode(), type);
81 } 94 }
95
96 public EquivalenceLiteral isEquivalent(DataVariable<T> other) {
97 return new EquivalenceLiteral(true, this, other);
98 }
99
100 public EquivalenceLiteral notEquivalent(DataVariable<T> other) {
101 return new EquivalenceLiteral(false, this, other);
102 }
82} 103}
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
index a2f3261f..53c32e20 100644
--- 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
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import org.jetbrains.annotations.Nullable; 8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.literal.ConstantLiteral; 10import tools.refinery.store.query.literal.ConstantLiteral;
10import tools.refinery.store.query.literal.EquivalenceLiteral; 11import tools.refinery.store.query.literal.EquivalenceLiteral;
11 12
@@ -22,11 +23,6 @@ public final class NodeVariable extends Variable {
22 } 23 }
23 24
24 @Override 25 @Override
25 public boolean isUnifiable() {
26 return true;
27 }
28
29 @Override
30 public NodeVariable renew(@Nullable String name) { 26 public NodeVariable renew(@Nullable String name) {
31 return Variable.of(name); 27 return Variable.of(name);
32 } 28 }
@@ -37,13 +33,28 @@ public final class NodeVariable extends Variable {
37 } 33 }
38 34
39 @Override 35 @Override
36 public boolean isNodeVariable() {
37 return true;
38 }
39
40 @Override
41 public boolean isDataVariable() {
42 return false;
43 }
44
45 @Override
40 public NodeVariable asNodeVariable() { 46 public NodeVariable asNodeVariable() {
41 return this; 47 return this;
42 } 48 }
43 49
44 @Override 50 @Override
45 public <T> DataVariable<T> asDataVariable(Class<T> type) { 51 public <T> DataVariable<T> asDataVariable(Class<T> type) {
46 throw new IllegalStateException("%s is a node variable".formatted(this)); 52 throw new InvalidQueryException("%s is a node variable".formatted(this));
53 }
54
55 @Override
56 public int hashCodeWithSubstitution(int sequenceNumber) {
57 return sequenceNumber;
47 } 58 }
48 59
49 public ConstantLiteral isConstant(int value) { 60 public ConstantLiteral isConstant(int value) {
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
index e5a0cdf1..577ac6e0 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Parameter.java
@@ -9,11 +9,15 @@ import java.util.Objects;
9import java.util.Optional; 9import java.util.Optional;
10 10
11public class Parameter { 11public class Parameter {
12 public static final Parameter NODE_OUT = new Parameter(null, ParameterDirection.OUT); 12 public static final Parameter NODE_OUT = new Parameter(null);
13 13
14 private final Class<?> dataType; 14 private final Class<?> dataType;
15 private final ParameterDirection direction; 15 private final ParameterDirection direction;
16 16
17 public Parameter(Class<?> dataType) {
18 this(dataType, ParameterDirection.OUT);
19 }
20
17 public Parameter(Class<?> dataType, ParameterDirection direction) { 21 public Parameter(Class<?> dataType, ParameterDirection direction) {
18 this.dataType = dataType; 22 this.dataType = dataType;
19 this.direction = direction; 23 this.direction = direction;
@@ -35,6 +39,10 @@ public class Parameter {
35 return direction; 39 return direction;
36 } 40 }
37 41
42 public boolean matches(Parameter other) {
43 return Objects.equals(dataType, other.dataType) && direction == other.direction;
44 }
45
38 public boolean isAssignable(Variable variable) { 46 public boolean isAssignable(Variable variable) {
39 if (variable instanceof AnyDataVariable dataVariable) { 47 if (variable instanceof AnyDataVariable dataVariable) {
40 return dataVariable.getType().equals(dataType); 48 return dataVariable.getType().equals(dataType);
@@ -50,7 +58,7 @@ public class Parameter {
50 if (this == o) return true; 58 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false; 59 if (o == null || getClass() != o.getClass()) return false;
52 Parameter parameter = (Parameter) o; 60 Parameter parameter = (Parameter) o;
53 return Objects.equals(dataType, parameter.dataType) && direction == parameter.direction; 61 return matches(parameter);
54 } 62 }
55 63
56 @Override 64 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
index cd0739be..da83f3c3 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ParameterDirection.java
@@ -6,8 +6,8 @@
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8public enum ParameterDirection { 8public enum ParameterDirection {
9 OUT("@Out"), 9 OUT("out"),
10 IN("@In"); 10 IN("in");
11 11
12 private final String name; 12 private final String name;
13 13
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
index a46ebe31..a464ece5 100644
--- 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
@@ -5,13 +5,17 @@
5 */ 5 */
6package tools.refinery.store.query.term; 6package tools.refinery.store.query.term;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.equality.LiteralEqualityHelper; 9import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.equality.LiteralHashCodeHelper;
9import tools.refinery.store.query.substitution.Substitution; 11import tools.refinery.store.query.substitution.Substitution;
10import tools.refinery.store.query.valuation.Valuation; 12import tools.refinery.store.query.valuation.Valuation;
11 13
12import java.util.Objects; 14import java.util.Objects;
13import java.util.Set; 15import java.util.Set;
14 16
17// {@link Object#equals(Object)} is implemented by {@link AbstractTerm}.
18@SuppressWarnings("squid:S2160")
15public abstract class UnaryTerm<R, T> extends AbstractTerm<R> { 19public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
16 private final Class<T> bodyType; 20 private final Class<T> bodyType;
17 private final Term<T> body; 21 private final Term<T> body;
@@ -19,7 +23,7 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
19 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) { 23 protected UnaryTerm(Class<R> type, Class<T> bodyType, Term<T> body) {
20 super(type); 24 super(type);
21 if (!body.getType().equals(bodyType)) { 25 if (!body.getType().equals(bodyType)) {
22 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body, 26 throw new InvalidQueryException("Expected body %s to be of type %s, got %s instead".formatted(body,
23 bodyType.getName(), body.getType().getName())); 27 bodyType.getName(), body.getType().getName()));
24 } 28 }
25 this.bodyType = bodyType; 29 this.bodyType = bodyType;
@@ -52,6 +56,11 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
52 } 56 }
53 57
54 @Override 58 @Override
59 public int hashCodeWithSubstitution(LiteralHashCodeHelper helper) {
60 return Objects.hash(super.hashCodeWithSubstitution(helper), bodyType, body.hashCodeWithSubstitution(helper));
61 }
62
63 @Override
55 public Term<R> substitute(Substitution substitution) { 64 public Term<R> substitute(Substitution substitution) {
56 return doSubstitute(substitution, body.substitute(substitution)); 65 return doSubstitute(substitution, body.substitute(substitution));
57 } 66 }
@@ -62,18 +71,4 @@ public abstract class UnaryTerm<R, T> extends AbstractTerm<R> {
62 public Set<AnyDataVariable> getInputVariables() { 71 public Set<AnyDataVariable> getInputVariables() {
63 return body.getInputVariables(); 72 return body.getInputVariables();
64 } 73 }
65
66 @Override
67 public boolean equals(Object o) {
68 if (this == o) return true;
69 if (o == null || getClass() != o.getClass()) return false;
70 if (!super.equals(o)) return false;
71 UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o;
72 return Objects.equals(bodyType, unaryTerm.bodyType) && Objects.equals(body, unaryTerm.body);
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hash(super.hashCode(), bodyType, body);
78 }
79} 74}
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
index a0268c8e..1b553704 100644
--- 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
@@ -38,16 +38,20 @@ public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
38 return uniqueName; 38 return uniqueName;
39 } 39 }
40 40
41 public abstract boolean isUnifiable();
42
43 public abstract Variable renew(@Nullable String name); 41 public abstract Variable renew(@Nullable String name);
44 42
45 public abstract Variable renew(); 43 public abstract Variable renew();
46 44
45 public abstract boolean isNodeVariable();
46
47 public abstract boolean isDataVariable();
48
47 public abstract NodeVariable asNodeVariable(); 49 public abstract NodeVariable asNodeVariable();
48 50
49 public abstract <T> DataVariable<T> asDataVariable(Class<T> type); 51 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
50 52
53 public abstract int hashCodeWithSubstitution(int sequenceNumber);
54
51 @Override 55 @Override
52 public String toString() { 56 public String toString() {
53 return getName(); 57 return getName();
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
index 5bbd3081..d31f00a2 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/uppercardinality/UpperCardinalitySumAggregator.java
@@ -70,7 +70,7 @@ public class UpperCardinalitySumAggregator implements StatefulAggregator<UpperCa
70 70
71 @Override 71 @Override
72 public UpperCardinality getResult() { 72 public UpperCardinality getResult() {
73 return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.valueOf(sumFiniteUpperBounds); 73 return countUnbounded > 0 ? UpperCardinalities.UNBOUNDED : UpperCardinalities.atMost(sumFiniteUpperBounds);
74 } 74 }
75 75
76 @Override 76 @Override
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
index b568b99d..2d8a10d1 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/utils/OrderStatisticTree.java
@@ -2,7 +2,7 @@
2 * Copyright (c) 2021 Rodion Efremov 2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 */ 6 */
7package tools.refinery.store.query.utils; 7package tools.refinery.store.query.utils;
8 8
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
index fd37604e..f130fa59 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AbstractFunctionView.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.view; 6package tools.refinery.store.query.view;
7 7
8import tools.refinery.store.map.CursorAsIterator;
8import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
9import tools.refinery.store.query.dnf.FunctionalDependency; 10import tools.refinery.store.query.dnf.FunctionalDependency;
10import tools.refinery.store.query.term.Parameter; 11import tools.refinery.store.query.term.Parameter;
@@ -83,6 +84,20 @@ public abstract class AbstractFunctionView<T> extends SymbolView<T> {
83 } 84 }
84 85
85 @Override 86 @Override
87 public boolean canIndexSlot(int slot) {
88 return slot >= 0 && slot < getSymbol().arity();
89 }
90
91 @Override
92 public Iterable<Object[]> getAdjacent(Model model, int slot, Object value) {
93 if (!(value instanceof Tuple1 tuple1)) {
94 return Set.of();
95 }
96 return (() -> new CursorAsIterator<>(model.getInterpretation(getSymbol()).getAdjacent(slot, tuple1.get(0)),
97 this::forwardMap, this::filter));
98 }
99
100 @Override
86 public List<Parameter> getParameters() { 101 public List<Parameter> getParameters() {
87 return parameters; 102 return parameters;
88 } 103 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
index 90b27ebb..7e9bf6df 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnySymbolView.java
@@ -28,4 +28,12 @@ public sealed interface AnySymbolView extends Constraint permits SymbolView {
28 boolean get(Model model, Object[] tuple); 28 boolean get(Model model, Object[] tuple);
29 29
30 Iterable<Object[]> getAll(Model model); 30 Iterable<Object[]> getAll(Model model);
31
32 default Iterable<Object[]> getAdjacent(Model model, int slot, Object value) {
33 throw new IllegalArgumentException("Cannot index slot " + slot);
34 }
35
36 default boolean canIndexSlot(int slot) {
37 return false;
38 }
31} 39}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
index abae6e5c..924277ed 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredView.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.view; 6package tools.refinery.store.query.view;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.representation.Symbol; 10import tools.refinery.store.representation.Symbol;
10 11
@@ -66,7 +67,7 @@ public class FilteredView<T> extends TuplePreservingView<T> {
66 // The predicate doesn't need to handle the default value if it is null. 67 // The predicate doesn't need to handle the default value if it is null.
67 } 68 }
68 if (matchesDefaultValue) { 69 if (matchesDefaultValue) {
69 throw new IllegalArgumentException("Tuples with default value %s cannot be enumerated in %s" 70 throw new InvalidQueryException("Tuples with default value %s cannot be enumerated in %s"
70 .formatted(defaultValue, getSymbol())); 71 .formatted(defaultValue, getSymbol()));
71 } 72 }
72 } 73 }
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
index 6bc5a708..ed12cd9d 100644
--- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingView.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.query.view; 6package tools.refinery.store.query.view;
7 7
8import tools.refinery.store.map.CursorAsIterator;
8import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Parameter; 10import tools.refinery.store.query.term.Parameter;
10import tools.refinery.store.representation.Symbol; 11import tools.refinery.store.representation.Symbol;
@@ -14,6 +15,7 @@ import tools.refinery.store.tuple.Tuple1;
14import java.util.Arrays; 15import java.util.Arrays;
15import java.util.List; 16import java.util.List;
16import java.util.Objects; 17import java.util.Objects;
18import java.util.Set;
17 19
18public abstract class TuplePreservingView<T> extends SymbolView<T> { 20public abstract class TuplePreservingView<T> extends SymbolView<T> {
19 private final List<Parameter> parameters; 21 private final List<Parameter> parameters;
@@ -56,6 +58,20 @@ public abstract class TuplePreservingView<T> extends SymbolView<T> {
56 } 58 }
57 59
58 @Override 60 @Override
61 public boolean canIndexSlot(int slot) {
62 return slot >= 0 && slot < getSymbol().arity();
63 }
64
65 @Override
66 public Iterable<Object[]> getAdjacent(Model model, int slot, Object value) {
67 if (!(value instanceof Tuple1 tuple1)) {
68 return Set.of();
69 }
70 return (() -> new CursorAsIterator<>(model.getInterpretation(getSymbol()).getAdjacent(slot, tuple1.get(0)),
71 this::forwardMap, this::filter));
72 }
73
74 @Override
59 public List<Parameter> getParameters() { 75 public List<Parameter> getParameters() {
60 return parameters; 76 return parameters;
61 } 77 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java
index e17496e3..6a2dc0c7 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfBuilderLiteralEliminationTest.java
@@ -20,7 +20,7 @@ import tools.refinery.store.representation.Symbol;
20import java.util.List; 20import java.util.List;
21 21
22import static org.hamcrest.MatcherAssert.assertThat; 22import static org.hamcrest.MatcherAssert.assertThat;
23import static tools.refinery.store.query.literal.Literals.assume; 23import static tools.refinery.store.query.literal.Literals.check;
24import static tools.refinery.store.query.literal.Literals.not; 24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; 25import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
26 26
@@ -47,7 +47,7 @@ class DnfBuilderLiteralEliminationTest {
47 void eliminateTrueAssumptionTest() { 47 void eliminateTrueAssumptionTest() {
48 var actual = Dnf.builder() 48 var actual = Dnf.builder()
49 .parameters(p, q) 49 .parameters(p, q)
50 .clause(assume(BoolTerms.constant(true)), friendView.call(p, q)) 50 .clause(check(BoolTerms.constant(true)), friendView.call(p, q))
51 .build(); 51 .build();
52 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); 52 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
53 53
@@ -75,7 +75,7 @@ class DnfBuilderLiteralEliminationTest {
75 var actual = Dnf.builder() 75 var actual = Dnf.builder()
76 .parameters(p, q) 76 .parameters(p, q)
77 .clause(friendView.call(p, q)) 77 .clause(friendView.call(p, q))
78 .clause(friendView.call(q, p), assume(BoolTerms.constant(value))) 78 .clause(friendView.call(q, p), check(BoolTerms.constant(value)))
79 .build(); 79 .build();
80 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build(); 80 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
81 81
@@ -207,4 +207,53 @@ class DnfBuilderLiteralEliminationTest {
207 207
208 assertThat(actual, structurallyEqualTo(expected)); 208 assertThat(actual, structurallyEqualTo(expected));
209 } 209 }
210
211 @Test
212 void removeContradictoryTest() {
213 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
214 friendView.call(p, q),
215 not(friendView.call(p, q))
216 )));
217 var expected = Dnf.builder().build();
218
219 assertThat(actual, structurallyEqualTo(expected));
220 }
221
222 @Test
223 void removeContradictoryUniversalTest() {
224 var actual = Dnf.of(builder -> builder.clause((p, q) -> List.of(
225 friendView.call(q, q),
226 friendView.call(p, q),
227 not(friendView.call(p, Variable.of()))
228 )));
229 var expected = Dnf.builder().build();
230
231 assertThat(actual, structurallyEqualTo(expected));
232 }
233
234 @Test
235 void removeContradictoryExistentialUniversalTest() {
236 var actual = Dnf.of(builder -> builder.clause((p) -> List.of(
237 friendView.call(p, Variable.of()),
238 not(friendView.call(p, Variable.of()))
239 )));
240 var expected = Dnf.builder().build();
241
242 assertThat(actual, structurallyEqualTo(expected));
243 }
244
245 @Test
246 void removeContradictoryUniversalParameterTest() {
247 var actual = Dnf.of(builder -> {
248 var p = builder.parameter("p");
249 builder.clause((q) -> List.of(
250 friendView.call(q, q),
251 friendView.call(p, q),
252 not(friendView.call(p, Variable.of()))
253 ));
254 });
255 var expected = Dnf.builder().parameter(p).build();
256
257 assertThat(actual, structurallyEqualTo(expected));
258 }
210} 259}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
index d75d7f17..12cfaa4e 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/DnfToDefinitionStringTest.java
@@ -50,7 +50,7 @@ class DnfToDefinitionStringTest {
50 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build(); 50 var dnf = Dnf.builder("Example").parameter(p, ParameterDirection.IN).clause().build();
51 51
52 assertThat(dnf.toDefinitionString(), is(""" 52 assertThat(dnf.toDefinitionString(), is("""
53 pred Example(@In p) <-> 53 pred Example(in p) <->
54 <empty>. 54 <empty>.
55 """)); 55 """));
56 } 56 }
@@ -73,7 +73,7 @@ class DnfToDefinitionStringTest {
73 .build(); 73 .build();
74 74
75 assertThat(dnf.toDefinitionString(), is(""" 75 assertThat(dnf.toDefinitionString(), is("""
76 pred Example(@In p) <-> 76 pred Example(in p) <->
77 !(@RelationView("key") friend(p, q)). 77 !(@RelationView("key") friend(p, q)).
78 """)); 78 """));
79 } 79 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/HashCodeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/HashCodeTest.java
new file mode 100644
index 00000000..0c8eaeed
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/HashCodeTest.java
@@ -0,0 +1,67 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.dnf;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.query.view.AnySymbolView;
12import tools.refinery.store.query.view.KeyOnlyView;
13import tools.refinery.store.representation.Symbol;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.is;
17import static org.hamcrest.Matchers.not;
18
19class HashCodeTest {
20 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
21 private static final Symbol<Boolean> friend = Symbol.of("friend", 2);
22 private static final AnySymbolView personView = new KeyOnlyView<>(person);
23 private static final AnySymbolView friendView = new KeyOnlyView<>(friend);
24 private static final NodeVariable p = Variable.of("p");
25 private static final NodeVariable q = Variable.of("q");
26
27 @Test
28 void flatEqualsTest() {
29 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
30 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
31
32 assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution()));
33 }
34
35 @Test
36 void flatNotEqualsTest() {
37 var expected = Dnf.builder("Expected").parameters(q).clause(friendView.call(q, q)).build();
38 var actual = Dnf.builder("Actual").parameters(p).clause(friendView.call(p, q)).build();
39
40 assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution()));
41 }
42
43 @Test
44 void deepEqualsTest() {
45 var expected2 = Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build();
46 var expected = Dnf.builder("Expected").parameters(q).clause(
47 expected2.call(q)
48 ).build();
49 var actual = Dnf.builder("Actual").parameters(q).clause(
50 expected2.call(q)
51 ).build();
52
53 assertThat(actual.hashCodeWithSubstitution(), is(expected.hashCodeWithSubstitution()));
54 }
55
56 @Test
57 void deepNotEqualsTest() {
58 var expected = Dnf.builder("Expected").parameters(q).clause(
59 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
60 ).build();
61 var actual = Dnf.builder("Actual").parameters(q).clause(
62 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
63 ).build();
64
65 assertThat(actual.hashCodeWithSubstitution(), not(expected.hashCodeWithSubstitution()));
66 }
67}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
index e22dbb21..854bd469 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/TopologicalSortTest.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.query.dnf; 6package tools.refinery.store.query.dnf;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.term.NodeVariable; 10import tools.refinery.store.query.term.NodeVariable;
10import tools.refinery.store.query.term.ParameterDirection; 11import tools.refinery.store.query.term.ParameterDirection;
11import tools.refinery.store.query.term.Variable; 12import tools.refinery.store.query.term.Variable;
@@ -80,7 +81,7 @@ class TopologicalSortTest {
80 example.call(r, t, q, s), 81 example.call(r, t, q, s),
81 friendView.call(r, t) 82 friendView.call(r, t)
82 ); 83 );
83 assertThrows(IllegalArgumentException.class, builder::build); 84 assertThrows(InvalidQueryException.class, builder::build);
84 } 85 }
85 86
86 @Test 87 @Test
@@ -93,7 +94,7 @@ class TopologicalSortTest {
93 example.call(p, q, r, s), 94 example.call(p, q, r, s),
94 example.call(r, t, q, s) 95 example.call(r, t, q, s)
95 ); 96 );
96 assertThrows(IllegalArgumentException.class, builder::build); 97 assertThrows(InvalidQueryException.class, builder::build);
97 } 98 }
98 99
99 @Test 100 @Test
@@ -107,6 +108,6 @@ class TopologicalSortTest {
107 example.call(r, t, q, s), 108 example.call(r, t, q, s),
108 example.call(p, q, r, t) 109 example.call(p, q, r, t)
109 ); 110 );
110 assertThrows(IllegalArgumentException.class, builder::build); 111 assertThrows(InvalidQueryException.class, builder::build);
111 } 112 }
112} 113}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
index c52d26b2..fc3f5d48 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/dnf/VariableDirectionTest.java
@@ -28,9 +28,8 @@ import static org.hamcrest.Matchers.hasItem;
28import static org.hamcrest.Matchers.not; 28import static org.hamcrest.Matchers.not;
29import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 29import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
30import static org.junit.jupiter.api.Assertions.assertThrows; 30import static org.junit.jupiter.api.Assertions.assertThrows;
31import static tools.refinery.store.query.literal.Literals.assume;
32import static tools.refinery.store.query.literal.Literals.not; 31import static tools.refinery.store.query.literal.Literals.not;
33import static tools.refinery.store.query.term.int_.IntTerms.*; 32import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
34 33
35class VariableDirectionTest { 34class VariableDirectionTest {
36 private static final Symbol<Boolean> person = Symbol.of("Person", 1); 35 private static final Symbol<Boolean> person = Symbol.of("Person", 1);
@@ -49,7 +48,7 @@ class VariableDirectionTest {
49 @MethodSource("clausesWithVariableInput") 48 @MethodSource("clausesWithVariableInput")
50 void unboundOutVariableTest(List<? extends Literal> clause) { 49 void unboundOutVariableTest(List<? extends Literal> clause) {
51 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause); 50 var builder = Dnf.builder().parameter(p, ParameterDirection.OUT).clause(clause);
52 assertThrows(IllegalArgumentException.class, builder::build); 51 assertThrows(InvalidClauseException.class, builder::build);
53 } 52 }
54 53
55 @ParameterizedTest 54 @ParameterizedTest
@@ -101,7 +100,7 @@ class VariableDirectionTest {
101 var clauseWithEquivalence = new ArrayList<Literal>(clause); 100 var clauseWithEquivalence = new ArrayList<Literal>(clause);
102 clauseWithEquivalence.add(r.isEquivalent(p)); 101 clauseWithEquivalence.add(r.isEquivalent(p));
103 var builder = Dnf.builder().clause(clauseWithEquivalence); 102 var builder = Dnf.builder().clause(clauseWithEquivalence);
104 assertThrows(IllegalArgumentException.class, builder::build); 103 assertThrows(InvalidClauseException.class, builder::build);
105 } 104 }
106 105
107 static Stream<Arguments> clausesNotBindingVariable() { 106 static Stream<Arguments> clausesNotBindingVariable() {
@@ -119,7 +118,7 @@ class VariableDirectionTest {
119 @MethodSource("literalsWithPrivateVariable") 118 @MethodSource("literalsWithPrivateVariable")
120 void unboundTwicePrivateVariableTest(Literal literal) { 119 void unboundTwicePrivateVariableTest(Literal literal) {
121 var builder = Dnf.builder().clause(not(personView.call(p)), literal); 120 var builder = Dnf.builder().clause(not(personView.call(p)), literal);
122 assertThrows(IllegalArgumentException.class, builder::build); 121 assertThrows(InvalidClauseException.class, builder::build);
123 } 122 }
124 123
125 @ParameterizedTest 124 @ParameterizedTest
@@ -127,7 +126,7 @@ class VariableDirectionTest {
127 void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) { 126 void unboundTwiceByEquivalencePrivateVariableTest(Literal literal) {
128 var r = Variable.of("r"); 127 var r = Variable.of("r");
129 var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal); 128 var builder = Dnf.builder().clause(not(personView.call(r)), r.isEquivalent(p), literal);
130 assertThrows(IllegalArgumentException.class, builder::build); 129 assertThrows(InvalidClauseException.class, builder::build);
131 } 130 }
132 131
133 static Stream<Arguments> literalsWithPrivateVariable() { 132 static Stream<Arguments> literalsWithPrivateVariable() {
@@ -160,7 +159,7 @@ class VariableDirectionTest {
160 @MethodSource("literalsWithRequiredVariableInput") 159 @MethodSource("literalsWithRequiredVariableInput")
161 void unboundPrivateVariableTest(Literal literal) { 160 void unboundPrivateVariableTest(Literal literal) {
162 var builder = Dnf.builder().clause(literal); 161 var builder = Dnf.builder().clause(literal);
163 assertThrows(IllegalArgumentException.class, builder::build); 162 assertThrows(InvalidClauseException.class, builder::build);
164 } 163 }
165 164
166 @ParameterizedTest 165 @ParameterizedTest
@@ -246,182 +245,6 @@ class VariableDirectionTest {
246 ); 245 );
247 } 246 }
248 247
249 @ParameterizedTest
250 @MethodSource("clausesWithDataVariableInput")
251 void unboundOutDataVariableTest(List<? extends Literal> clause) {
252 var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(clause);
253 assertThrows(IllegalArgumentException.class, builder::build);
254 }
255
256 @ParameterizedTest
257 @MethodSource("clausesWithDataVariableInput")
258 void unboundInDataVariableTest(List<? extends Literal> clause) {
259 var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(clause);
260 var dnf = assertDoesNotThrow(builder::build);
261 var clauses = dnf.getClauses();
262 if (clauses.size() > 0) {
263 assertThat(clauses.get(0).positiveVariables(), hasItem(x));
264 }
265 }
266
267 @ParameterizedTest
268 @MethodSource("clausesWithDataVariableInput")
269 void boundPrivateDataVariableTest(List<? extends Literal> clause) {
270 var clauseWithBinding = new ArrayList<Literal>(clause);
271 clauseWithBinding.add(x.assign(constant(27)));
272 var builder = Dnf.builder().clause(clauseWithBinding);
273 var dnf = assertDoesNotThrow(builder::build);
274 var clauses = dnf.getClauses();
275 if (clauses.size() > 0) {
276 assertThat(clauses.get(0).positiveVariables(), hasItem(x));
277 }
278 }
279
280 static Stream<Arguments> clausesWithDataVariableInput() {
281 return Stream.concat(
282 clausesNotBindingDataVariable(),
283 literalToClauseArgumentStream(literalsWithRequiredDataVariableInput())
284 );
285 }
286
287 @ParameterizedTest
288 @MethodSource("clausesNotBindingDataVariable")
289 void unboundPrivateDataVariableTest(List<? extends Literal> clause) {
290 var builder = Dnf.builder().clause(clause);
291 var dnf = assertDoesNotThrow(builder::build);
292 var clauses = dnf.getClauses();
293 if (clauses.size() > 0) {
294 assertThat(clauses.get(0).positiveVariables(), not(hasItem(x)));
295 }
296 }
297
298 static Stream<Arguments> clausesNotBindingDataVariable() {
299 return Stream.concat(
300 Stream.of(
301 Arguments.of(List.of()),
302 Arguments.of(List.of(BooleanLiteral.TRUE)),
303 Arguments.of(List.of(BooleanLiteral.FALSE))
304 ),
305 literalToClauseArgumentStream(literalsWithPrivateDataVariable())
306 );
307 }
308
309 @ParameterizedTest
310 @MethodSource("literalsWithPrivateDataVariable")
311 void unboundTwicePrivateDataVariableTest(Literal literal) {
312 var builder = Dnf.builder().clause(not(ageView.call(p, x)), literal);
313 assertThrows(IllegalArgumentException.class, builder::build);
314 }
315
316 static Stream<Arguments> literalsWithPrivateDataVariable() {
317 var dnfWithOutput = Dnf.builder("WithDataOutput")
318 .parameter(y, ParameterDirection.OUT)
319 .parameter(q, ParameterDirection.OUT)
320 .clause(ageView.call(q, y))
321 .build();
322
323 return Stream.of(
324 Arguments.of(not(ageView.call(q, x))),
325 Arguments.of(y.assign(ageView.count(q, x))),
326 Arguments.of(not(dnfWithOutput.call(x, q)))
327 );
328 }
329
330 @ParameterizedTest
331 @MethodSource("literalsWithRequiredDataVariableInput")
332 void unboundPrivateDataVariableTest(Literal literal) {
333 var builder = Dnf.builder().clause(literal);
334 assertThrows(IllegalArgumentException.class, builder::build);
335 }
336
337 static Stream<Arguments> literalsWithRequiredDataVariableInput() {
338 var dnfWithInput = Dnf.builder("WithDataInput")
339 .parameter(y, ParameterDirection.IN)
340 .parameter(q, ParameterDirection.OUT)
341 .clause(ageView.call(q, x))
342 .build();
343 // We are passing {@code y} to the parameter named {@code right} of {@code greaterEq}.
344 @SuppressWarnings("SuspiciousNameCombination")
345 var dnfWithInputToAggregate = Dnf.builder("WithDataInputToAggregate")
346 .parameter(y, ParameterDirection.IN)
347 .parameter(q, ParameterDirection.OUT)
348 .parameter(x, ParameterDirection.OUT)
349 .clause(
350 friendView.call(p, q),
351 ageView.call(q, x),
352 assume(greaterEq(x, y))
353 )
354 .build();
355
356 return Stream.of(
357 Arguments.of(dnfWithInput.call(x, q)),
358 Arguments.of(not(dnfWithInput.call(x, q))),
359 Arguments.of(y.assign(dnfWithInput.count(x, q))),
360 Arguments.of(y.assign(dnfWithInputToAggregate.aggregateBy(z, INT_SUM, x, q, z)))
361 );
362 }
363
364 @ParameterizedTest
365 @MethodSource("literalsWithDataVariableOutput")
366 void boundDataParameterTest(Literal literal) {
367 var builder = Dnf.builder().parameter(x, ParameterDirection.OUT).clause(literal);
368 var dnf = assertDoesNotThrow(builder::build);
369 assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x));
370 }
371
372 @ParameterizedTest
373 @MethodSource("literalsWithDataVariableOutput")
374 void boundTwiceDataParameterTest(Literal literal) {
375 var builder = Dnf.builder().parameter(x, ParameterDirection.IN).clause(literal);
376 assertThrows(IllegalArgumentException.class, builder::build);
377 }
378
379 @ParameterizedTest
380 @MethodSource("literalsWithDataVariableOutput")
381 void boundPrivateDataVariableOutputTest(Literal literal) {
382 var dnfWithInput = Dnf.builder("WithInput")
383 .parameter(x, ParameterDirection.IN)
384 .clause(assume(greaterEq(x, constant(24))))
385 .build();
386 var builder = Dnf.builder().clause(dnfWithInput.call(x), literal);
387 var dnf = assertDoesNotThrow(builder::build);
388 assertThat(dnf.getClauses().get(0).positiveVariables(), hasItem(x));
389 }
390
391 @ParameterizedTest
392 @MethodSource("literalsWithDataVariableOutput")
393 void boundTwicePrivateDataVariableOutputTest(Literal literal) {
394 var builder = Dnf.builder().clause(x.assign(constant(27)), literal);
395 assertThrows(IllegalArgumentException.class, builder::build);
396 }
397
398 static Stream<Arguments> literalsWithDataVariableOutput() {
399 var dnfWithOutput = Dnf.builder("WithOutput")
400 .parameter(q, ParameterDirection.OUT)
401 .clause(personView.call(q))
402 .build();
403 var dnfWithDataOutput = Dnf.builder("WithDataOutput")
404 .parameter(y, ParameterDirection.OUT)
405 .parameter(q, ParameterDirection.OUT)
406 .clause(ageView.call(q, y))
407 .build();
408 var dnfWithOutputToAggregate = Dnf.builder("WithDataOutputToAggregate")
409 .parameter(q, ParameterDirection.OUT)
410 .parameter(y, ParameterDirection.OUT)
411 .clause(ageView.call(q, y))
412 .build();
413
414 return Stream.of(
415 Arguments.of(x.assign(constant(24))),
416 Arguments.of(ageView.call(q, x)),
417 Arguments.of(x.assign(personView.count(q))),
418 Arguments.of(x.assign(ageView.aggregate(INT_SUM, q))),
419 Arguments.of(dnfWithDataOutput.call(x, q)),
420 Arguments.of(x.assign(dnfWithOutput.count(q))),
421 Arguments.of(x.assign(dnfWithOutputToAggregate.aggregateBy(z, INT_SUM, q, z)))
422 );
423 }
424
425 private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) { 248 private static Stream<Arguments> literalToClauseArgumentStream(Stream<Arguments> literalArgumentsStream) {
426 return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0]))); 249 return literalArgumentsStream.map(arguments -> Arguments.of(List.of(arguments.get()[0])));
427 } 250 }
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
index 35910e08..ddd57e96 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/literal/AggregationLiteralTest.java
@@ -7,15 +7,16 @@ package tools.refinery.store.query.literal;
7 7
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import tools.refinery.store.query.Constraint; 9import tools.refinery.store.query.Constraint;
10import tools.refinery.store.query.InvalidQueryException;
10import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.InvalidClauseException;
11import tools.refinery.store.query.term.*; 13import tools.refinery.store.query.term.*;
12 14
13import java.util.List; 15import java.util.List;
14import java.util.Set; 16import java.util.Set;
15 17
16import static org.hamcrest.MatcherAssert.assertThat; 18import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.containsInAnyOrder; 19import static org.hamcrest.Matchers.*;
18import static org.hamcrest.Matchers.empty;
19import static org.junit.jupiter.api.Assertions.assertAll; 20import static org.junit.jupiter.api.Assertions.assertAll;
20import static org.junit.jupiter.api.Assertions.assertThrows; 21import static org.junit.jupiter.api.Assertions.assertThrows;
21import static tools.refinery.store.query.literal.Literals.not; 22import static tools.refinery.store.query.literal.Literals.not;
@@ -57,13 +58,13 @@ class AggregationLiteralTest {
57 @Test 58 @Test
58 void missingAggregationVariableTest() { 59 void missingAggregationVariableTest() {
59 var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z); 60 var aggregation = fakeConstraint.aggregateBy(y, INT_SUM, p, z);
60 assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); 61 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
61 } 62 }
62 63
63 @Test 64 @Test
64 void circularAggregationVariableTest() { 65 void circularAggregationVariableTest() {
65 var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x); 66 var aggregation = fakeConstraint.aggregateBy(x, INT_SUM, p, x);
66 assertThrows(IllegalArgumentException.class, () -> x.assign(aggregation)); 67 assertThrows(InvalidQueryException.class, () -> x.assign(aggregation));
67 } 68 }
68 69
69 @Test 70 @Test
@@ -73,7 +74,7 @@ class AggregationLiteralTest {
73 not(fakeConstraint.call(p, y)), 74 not(fakeConstraint.call(p, y)),
74 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) 75 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
75 ); 76 );
76 assertThrows(IllegalArgumentException.class, builder::build); 77 assertThrows(InvalidClauseException.class, builder::build);
77 } 78 }
78 79
79 @Test 80 @Test
@@ -83,6 +84,6 @@ class AggregationLiteralTest {
83 y.assign(constant(27)), 84 y.assign(constant(27)),
84 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y)) 85 x.assign(fakeConstraint.aggregateBy(y, INT_SUM, p, y))
85 ); 86 );
86 assertThrows(IllegalArgumentException.class, builder::build); 87 assertThrows(InvalidClauseException.class, builder::build);
87 } 88 }
88} 89}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/DuplicateDnfRemoverTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/DuplicateDnfRemoverTest.java
new file mode 100644
index 00000000..ebb24ab5
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/DuplicateDnfRemoverTest.java
@@ -0,0 +1,164 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.literal.AbstractCallLiteral;
12import tools.refinery.store.query.literal.Reduction;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17
18import java.util.List;
19
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.hamcrest.Matchers.is;
22import static org.hamcrest.Matchers.not;
23import static tools.refinery.store.query.literal.Literals.not;
24
25class DuplicateDnfRemoverTest {
26 private final static Symbol<Boolean> friend = Symbol.of("friend", 2);
27 private final static AnySymbolView friendView = new KeyOnlyView<>(friend);
28
29 private DuplicateDnfRemover sut;
30
31 @BeforeEach
32 void beforeEach() {
33 sut = new DuplicateDnfRemover();
34 }
35
36 @Test
37 void removeDuplicateSimpleTest() {
38 var one = Query.of("One", (builder, x, y) -> builder.clause(
39 friendView.call(x, y),
40 friendView.call(y, x)
41 ));
42 var two = Query.of("Two", (builder, x, y) -> builder.clause(
43 friendView.call(x, y),
44 friendView.call(y, x)
45 ));
46
47 var oneResult = sut.rewrite(one);
48 var twoResult = sut.rewrite(two);
49
50 assertThat(oneResult, is(twoResult));
51 assertThat(one, is(oneResult));
52 }
53
54 @Test
55 void notDuplicateSimpleTest() {
56 var one = Query.of("One", (builder, x, y) -> builder.clause(
57 friendView.call(x, y),
58 friendView.call(y, x)
59 ));
60 var two = Query.of("Two", (builder, x, y) -> builder.clause((z) -> List.of(
61 friendView.call(x, y),
62 friendView.call(y, z)
63 )));
64
65 var oneResult = sut.rewrite(one);
66 var twoResult = sut.rewrite(two);
67
68 assertThat(one, is(oneResult));
69 assertThat(two, is(twoResult));
70 }
71
72 @Test
73 void removeDuplicateRecursiveTest() {
74 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
75 friendView.call(x, y),
76 friendView.call(y, x)
77 ));
78 var one = Query.of("One", (builder, x) -> builder.clause(
79 oneSubQuery.call(x, Variable.of())
80 ));
81 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
82 friendView.call(x, y),
83 friendView.call(y, x)
84 ));
85 var two = Query.of("Two", (builder, x) -> builder.clause(
86 twoSubQuery.call(x, Variable.of())
87 ));
88
89 var oneResult = sut.rewrite(one);
90 var twoResult = sut.rewrite(two);
91
92 assertThat(oneResult, is(twoResult));
93 assertThat(one, is(oneResult));
94 }
95
96 @Test
97 void notDuplicateRecursiveTest() {
98 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
99 friendView.call(x, y),
100 friendView.call(y, x)
101 ));
102 var one = Query.of("One", (builder, x) -> builder.clause(
103 oneSubQuery.call(x, Variable.of())
104 ));
105 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
106 friendView.call(x, y),
107 friendView.call(y, x)
108 ));
109 var two = Query.of("Two", (builder, x) -> builder.clause(
110 twoSubQuery.call(Variable.of(), x)
111 ));
112
113 var oneResult = sut.rewrite(one);
114 var twoResult = sut.rewrite(two);
115
116 assertThat(one, is(oneResult));
117 assertThat(oneResult, is(not(twoResult)));
118
119 var oneCall = (AbstractCallLiteral) oneResult.getDnf().getClauses().get(0).literals().get(0);
120 var twoCall = (AbstractCallLiteral) twoResult.getDnf().getClauses().get(0).literals().get(0);
121
122 assertThat(oneCall.getTarget(), is(twoCall.getTarget()));
123 }
124
125 @Test
126 void removeContradictionTest() {
127 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
128 friendView.call(x, y),
129 friendView.call(y, x)
130 ));
131 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
132 friendView.call(x, y),
133 friendView.call(y, x)
134 ));
135 var query = Query.of("Contradiction", (builder, x, y) -> builder.clause(
136 oneSubQuery.call(x, y),
137 not(twoSubQuery.call(x, y))
138 ));
139
140 var result = sut.rewrite(query);
141
142 assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE));
143 }
144
145 @Test
146 void removeQuantifiedContradictionTest() {
147 var oneSubQuery = Query.of("OneSubQuery", (builder, x, y) -> builder.clause(
148 friendView.call(x, y),
149 friendView.call(y, x)
150 ));
151 var twoSubQuery = Query.of("TwoSubQuery", (builder, x, y) -> builder.clause(
152 friendView.call(x, y),
153 friendView.call(y, x)
154 ));
155 var query = Query.of("Contradiction", (builder, x) -> builder.clause(
156 oneSubQuery.call(x, Variable.of()),
157 not(twoSubQuery.call(x, Variable.of()))
158 ));
159
160 var result = sut.rewrite(query);
161
162 assertThat(result.getDnf().getReduction(), is(Reduction.ALWAYS_FALSE));
163 }
164}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/InputParameterResolverTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/InputParameterResolverTest.java
new file mode 100644
index 00000000..ef0077e4
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/rewriter/InputParameterResolverTest.java
@@ -0,0 +1,228 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.query.rewriter;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.query.view.KeyOnlyView;
16import tools.refinery.store.representation.Symbol;
17
18import java.util.List;
19
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.hamcrest.Matchers.is;
22import static tools.refinery.store.query.literal.Literals.not;
23import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
24
25class InputParameterResolverTest {
26 private final static Symbol<Boolean> person = Symbol.of("Person", 1);
27 private final static Symbol<Boolean> friend = Symbol.of("friend", 2);
28 private final static AnySymbolView personView = new KeyOnlyView<>(person);
29 private final static AnySymbolView friendView = new KeyOnlyView<>(friend);
30
31 private InputParameterResolver sut;
32
33 @BeforeEach
34 void beforeEach() {
35 sut = new InputParameterResolver();
36 }
37
38 @Test
39 void inlineSingleClauseTest() {
40 var dnf = Dnf.of("SubQuery", builder -> {
41 var x = builder.parameter("x", ParameterDirection.OUT);
42 builder.clause(friendView.call(x, Variable.of()));
43 });
44 var query = Query.of("Actual", (builder, x) -> builder.clause(
45 dnf.call(x),
46 personView.call(x)
47 ));
48
49 var actual = sut.rewrite(query);
50
51 var expected = Query.of("Expected", (builder, x) -> builder.clause(
52 friendView.call(x, Variable.of()),
53 personView.call(x)
54 ));
55
56 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
57 }
58
59 @Test
60 void inlineSingleClauseWIthInputTest() {
61 var dnf = Dnf.of("SubQuery", builder -> {
62 var x = builder.parameter("x", ParameterDirection.IN);
63 builder.clause(not(friendView.call(x, Variable.of())));
64 });
65 var query = Query.of("Actual", (builder, x) -> builder.clause(
66 dnf.call(x),
67 personView.call(x)
68 ));
69
70 var actual = sut.rewrite(query);
71
72 var expected = Query.of("Expected", (builder, x) -> builder.clause(
73 personView.call(x),
74 not(friendView.call(x, Variable.of()))
75 ));
76
77 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
78 }
79
80 @Test
81 void singleLiteralDemandSetTest() {
82 var dnf = Dnf.of("SubQuery", builder -> {
83 var x = builder.parameter("x", ParameterDirection.IN);
84 builder.clause(not(friendView.call(x, Variable.of())));
85 builder.clause(not(friendView.call(Variable.of(), x)));
86 });
87 var query = Query.of("Actual", (builder, x) -> builder.clause(
88 dnf.call(x),
89 personView.call(x)
90 ));
91
92 var actual = sut.rewrite(query);
93
94 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
95 var x = builder.parameter("x", ParameterDirection.OUT);
96 builder.clause(
97 personView.call(x),
98 not(friendView.call(x, Variable.of()))
99 );
100 builder.clause(
101 personView.call(x),
102 not(friendView.call(Variable.of(), x))
103 );
104 });
105 var expected = Query.of("Expected", (builder, x) -> builder.clause(
106 personView.call(x),
107 expectedSubQuery.call(x)
108 ));
109
110 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
111 }
112
113 @Test
114 void multipleLiteralDemandSetTest() {
115 var dnf = Dnf.of("SubQuery", builder -> {
116 var x = builder.parameter("x", ParameterDirection.IN);
117 var y = builder.parameter("y", ParameterDirection.IN);
118 builder.clause(not(friendView.call(x, y)));
119 builder.clause(not(friendView.call(y, x)));
120 });
121 var query = Query.of("Actual", (builder, p1) -> builder.clause(p2 -> List.of(
122 not(dnf.call(p1, p2)),
123 personView.call(p1),
124 personView.call(p2)
125 )));
126
127 var actual = sut.rewrite(query);
128
129 var context = Dnf.of("Context", builder -> {
130 var x = builder.parameter("x", ParameterDirection.OUT);
131 var y = builder.parameter("y", ParameterDirection.OUT);
132 builder.clause(
133 personView.call(x),
134 personView.call(y)
135 );
136 });
137 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
138 var x = builder.parameter("x", ParameterDirection.OUT);
139 var y = builder.parameter("x", ParameterDirection.OUT);
140 builder.clause(
141 context.call(x, y),
142 not(friendView.call(x, y))
143 );
144 builder.clause(
145 context.call(x, y),
146 not(friendView.call(y, x))
147 );
148 });
149 var expected = Query.of("Expected", (builder, p1) -> builder.clause(p2 -> List.of(
150 context.call(p1, p2),
151 not(expectedSubQuery.call(p1, p2))
152 )));
153
154 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
155 }
156
157 @Test
158 void multipleParameterDemandSetTest() {
159 var dnf = Dnf.of("SubQuery", builder -> {
160 var x = builder.parameter("x", ParameterDirection.IN);
161 var y = builder.parameter("y", ParameterDirection.IN);
162 builder.clause(not(friendView.call(x, y)));
163 builder.clause(not(friendView.call(y, x)));
164 });
165 var query = Query.of("Actual", (builder, p1) -> builder.clause(
166 not(dnf.call(p1, p1)),
167 personView.call(p1)
168 ));
169
170 var actual = sut.rewrite(query);
171
172 var expectedSubQuery = Dnf.of("ExpectedSubQuery", builder -> {
173 var x = builder.parameter("x", ParameterDirection.OUT);
174 var y = builder.parameter("y", ParameterDirection.OUT);
175 builder.clause(
176 y.isEquivalent(x),
177 personView.call(x),
178 not(friendView.call(x, x))
179 );
180 });
181 var expected = Query.of("Expected", (builder, p1) -> builder.clause(
182 personView.call(p1),
183 not(expectedSubQuery.call(p1, p1))
184 ));
185
186 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
187 }
188
189 @Test
190 void eliminateDoubleNegationTest() {
191 var dnf = Dnf.of("SubQuery", builder -> {
192 var x = builder.parameter("x", ParameterDirection.IN);
193 builder.clause(not(friendView.call(x, Variable.of())));
194 });
195 var query = Query.of("Actual", (builder, p1) -> builder.clause(
196 personView.call(p1),
197 not(dnf.call(p1))
198 ));
199
200 var actual = sut.rewrite(query);
201
202 var expected = Query.of("Actual", (builder, p1) -> builder.clause(
203 personView.call(p1),
204 friendView.call(p1, Variable.of())
205 ));
206
207 assertThat(actual.getDnf(), structurallyEqualTo(expected.getDnf()));
208 }
209
210 @Test
211 void identityWhenNoWorkToDoTest() {
212 var dnf = Dnf.of("SubQuery", builder -> {
213 var x = builder.parameter("x", ParameterDirection.OUT);
214 builder.clause(
215 personView.call(x),
216 not(friendView.call(x, Variable.of()))
217 );
218 });
219 var query = Query.of("Actual", (builder, p1) -> builder.clause(
220 personView.call(p1),
221 not(dnf.call(p1))
222 ));
223
224 var actual = sut.rewrite(query);
225
226 assertThat(actual, is(query));
227 }
228}
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java
index 1cbc101a..1fae2492 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/term/TermSubstitutionTest.java
@@ -9,8 +9,8 @@ import org.junit.jupiter.api.Assertions;
9import org.junit.jupiter.params.ParameterizedTest; 9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.Arguments; 10import org.junit.jupiter.params.provider.Arguments;
11import org.junit.jupiter.params.provider.MethodSource; 11import org.junit.jupiter.params.provider.MethodSource;
12import tools.refinery.store.query.dnf.Dnf; 12import tools.refinery.store.query.equality.DnfEqualityChecker;
13import tools.refinery.store.query.equality.LiteralEqualityHelper; 13import tools.refinery.store.query.equality.SubstitutingLiteralEqualityHelper;
14import tools.refinery.store.query.substitution.Substitution; 14import tools.refinery.store.query.substitution.Substitution;
15import tools.refinery.store.query.term.bool.BoolTerms; 15import tools.refinery.store.query.term.bool.BoolTerms;
16import tools.refinery.store.query.term.int_.IntTerms; 16import tools.refinery.store.query.term.int_.IntTerms;
@@ -48,7 +48,7 @@ class TermSubstitutionTest {
48 void substitutionTest(AnyTerm term) { 48 void substitutionTest(AnyTerm term) {
49 var substitutedTerm1 = term.substitute(substitution); 49 var substitutedTerm1 = term.substitute(substitution);
50 Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term"); 50 Assertions.assertNotEquals(term, substitutedTerm1, "Original term is not equal to substituted term");
51 var helper = new LiteralEqualityHelper(Dnf::equals, List.of(), List.of()); 51 var helper = new SubstitutingLiteralEqualityHelper(DnfEqualityChecker.DEFAULT, List.of(), List.of());
52 Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper"); 52 Assertions.assertTrue(term.equalsWithSubstitution(helper, substitutedTerm1), "Terms are equal by helper");
53 // The {@link #substitution} is its own inverse. 53 // The {@link #substitution} is its own inverse.
54 var substitutedTerm2 = substitutedTerm1.substitute(substitution); 54 var substitutedTerm2 = substitutedTerm1.substitute(substitution);
diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
index cbb48603..0ac88ed7 100644
--- a/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/utils/OrderStatisticTreeTest.java
@@ -2,7 +2,7 @@
2 * Copyright (c) 2021 Rodion Efremov 2 * Copyright (c) 2021 Rodion Efremov
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/> 3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * 4 *
5 * SPDX-License-Identifier: MIT OR EPL-2.0 5 * SPDX-License-Identifier: MIT
6 */ 6 */
7package tools.refinery.store.query.utils; 7package tools.refinery.store.query.utils;
8 8
diff --git a/subprojects/store-reasoning-scope/build.gradle.kts b/subprojects/store-reasoning-scope/build.gradle.kts
new file mode 100644
index 00000000..6e41dc8e
--- /dev/null
+++ b/subprojects/store-reasoning-scope/build.gradle.kts
@@ -0,0 +1,17 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 api(project(":refinery-store-reasoning"))
13 implementation(libs.eclipseCollections.api)
14 implementation(libs.ortools)
15 runtimeOnly(libs.eclipseCollections)
16 testImplementation(project(":refinery-store-query-viatra"))
17}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorAdapter.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorAdapter.java
new file mode 100644
index 00000000..c2d3f59e
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorAdapter.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.reasoning.refinement.RefinementResult;
10import tools.refinery.store.reasoning.scope.internal.ScopePropagatorBuilderImpl;
11
12public interface ScopePropagatorAdapter extends ModelAdapter {
13 @Override
14 ScopePropagatorStoreAdapter getStoreAdapter();
15
16 RefinementResult propagate();
17
18 static ScopePropagatorBuilder builder() {
19 return new ScopePropagatorBuilderImpl();
20 }
21}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorBuilder.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorBuilder.java
new file mode 100644
index 00000000..a17e04a4
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorBuilder.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope;
7
8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.cardinality.CardinalityInterval;
13
14public interface ScopePropagatorBuilder extends ModelAdapterBuilder {
15 ScopePropagatorBuilder countSymbol(Symbol<CardinalityInterval> countSymbol);
16
17 ScopePropagatorBuilder scope(PartialRelation type, CardinalityInterval interval);
18
19 @Override
20 ScopePropagatorStoreAdapter build(ModelStore store);
21}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorStoreAdapter.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorStoreAdapter.java
new file mode 100644
index 00000000..65d9c38d
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/ScopePropagatorStoreAdapter.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope;
7
8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11
12import java.util.Map;
13
14public interface ScopePropagatorStoreAdapter extends ModelStoreAdapter {
15 Map<PartialRelation, CardinalityInterval> getScopes();
16}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/LowerTypeScopePropagator.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/LowerTypeScopePropagator.java
new file mode 100644
index 00000000..393c4b72
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/LowerTypeScopePropagator.java
@@ -0,0 +1,86 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
9import tools.refinery.store.dse.transition.objectives.Criteria;
10import tools.refinery.store.dse.transition.objectives.Objectives;
11import tools.refinery.store.model.ModelStoreBuilder;
12import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.query.term.Variable;
16import tools.refinery.store.reasoning.ReasoningBuilder;
17import tools.refinery.store.reasoning.literal.CountCandidateLowerBoundLiteral;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19
20import java.util.Collection;
21import java.util.List;
22
23import static tools.refinery.store.query.literal.Literals.check;
24import static tools.refinery.store.query.term.int_.IntTerms.*;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
26import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
27
28class LowerTypeScopePropagator extends TypeScopePropagator {
29 private final int lowerBound;
30
31 private LowerTypeScopePropagator(ScopePropagatorAdapterImpl adapter, int lowerBound, RelationalQuery allQuery,
32 RelationalQuery multiQuery) {
33 super(adapter, allQuery, multiQuery);
34 this.lowerBound = lowerBound;
35 }
36
37 @Override
38 public void updateBounds() {
39 constraint.setLb((lowerBound - getSingleCount()));
40 }
41
42 public static class Factory extends TypeScopePropagator.Factory {
43 private final PartialRelation type;
44 private final int lowerBound;
45 private final RelationalQuery allMay;
46 private final RelationalQuery multiMay;
47
48 public Factory(PartialRelation type, int lowerBound) {
49 this.type = type;
50 this.lowerBound = lowerBound;
51 allMay = Query.of(type.name() + "#may", (builder, instance) -> builder.clause(
52 may(type.call(instance))
53 ));
54 multiMay = Query.of(type.name() + "#multiMay", (builder, instance) -> builder.clause(
55 may(type.call(instance)),
56 MULTI_VIEW.call(instance)
57 ));
58 }
59
60 @Override
61 public TypeScopePropagator createPropagator(ScopePropagatorAdapterImpl adapter) {
62 return new LowerTypeScopePropagator(adapter, lowerBound, allMay, multiMay);
63 }
64
65 @Override
66 protected Collection<AnyQuery> getQueries() {
67 return List.of(allMay, multiMay);
68 }
69
70 @Override
71 public void configure(ModelStoreBuilder storeBuilder) {
72 super.configure(storeBuilder);
73
74 var requiredObjects = Query.of(type.name() + "#required", Integer.class, (builder, output) -> builder
75 .clause(Integer.class, currentCount -> List.of(
76 new CountCandidateLowerBoundLiteral(currentCount, type, List.of(Variable.of())),
77 output.assign(sub(currentCount, constant(lowerBound))),
78 check(greater(currentCount, constant(0)))
79 )));
80
81 storeBuilder.getAdapter(ReasoningBuilder.class).objective(Objectives.value(requiredObjects));
82 storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class).ifPresent(dseBuilder ->
83 dseBuilder.accept(Criteria.whenNoMatch(requiredObjects)));
84 }
85 }
86}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/RoundingUtil.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/RoundingUtil.java
new file mode 100644
index 00000000..a6d663ea
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/RoundingUtil.java
@@ -0,0 +1,26 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8final class RoundingUtil {
9 private static final double TOLERANCE = 0.01;
10
11 private RoundingUtil() {
12 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
13 }
14
15 public static int roundUp(double value) {
16 double ceil = Math.ceil(value - TOLERANCE);
17 int intCeil = (int) ceil;
18 return Math.max(intCeil, 0);
19 }
20
21 public static int roundDown(double value) {
22 double floor = Math.floor(value + TOLERANCE);
23 int intFloor = (int) floor;
24 return Math.max(intFloor, 0);
25 }
26}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java
new file mode 100644
index 00000000..99c501ce
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorAdapterImpl.java
@@ -0,0 +1,253 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import com.google.ortools.linearsolver.MPConstraint;
9import com.google.ortools.linearsolver.MPObjective;
10import com.google.ortools.linearsolver.MPSolver;
11import com.google.ortools.linearsolver.MPVariable;
12import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
13import org.eclipse.collections.api.factory.primitive.IntSets;
14import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
15import org.eclipse.collections.api.set.primitive.MutableIntSet;
16import tools.refinery.store.model.Interpretation;
17import tools.refinery.store.model.Model;
18import tools.refinery.store.query.ModelQueryAdapter;
19import tools.refinery.store.reasoning.refinement.RefinementResult;
20import tools.refinery.store.reasoning.scope.ScopePropagatorAdapter;
21import tools.refinery.store.reasoning.scope.ScopePropagatorStoreAdapter;
22import tools.refinery.store.representation.cardinality.*;
23import tools.refinery.store.tuple.Tuple;
24
25class ScopePropagatorAdapterImpl implements ScopePropagatorAdapter {
26 private final Model model;
27 private final ScopePropagatorStoreAdapterImpl storeAdapter;
28 private final ModelQueryAdapter queryEngine;
29 private final Interpretation<CardinalityInterval> countInterpretation;
30 private final MPSolver solver;
31 private final MPObjective objective;
32 private final MutableIntObjectMap<MPVariable> variables = IntObjectMaps.mutable.empty();
33 private final MutableIntSet activeVariables = IntSets.mutable.empty();
34 private final TypeScopePropagator[] propagators;
35 private boolean changed = true;
36
37 public ScopePropagatorAdapterImpl(Model model, ScopePropagatorStoreAdapterImpl storeAdapter) {
38 this.model = model;
39 this.storeAdapter = storeAdapter;
40 queryEngine = model.getAdapter(ModelQueryAdapter.class);
41 countInterpretation = model.getInterpretation(storeAdapter.getCountSymbol());
42 solver = MPSolver.createSolver("GLOP");
43 objective = solver.objective();
44 initializeVariables();
45 countInterpretation.addListener(this::countChanged, true);
46 var propagatorFactories = storeAdapter.getPropagatorFactories();
47 propagators = new TypeScopePropagator[propagatorFactories.size()];
48 for (int i = 0; i < propagators.length; i++) {
49 propagators[i] = propagatorFactories.get(i).createPropagator(this);
50 }
51 }
52
53 @Override
54 public Model getModel() {
55 return model;
56 }
57
58 @Override
59 public ScopePropagatorStoreAdapter getStoreAdapter() {
60 return storeAdapter;
61 }
62
63 private void initializeVariables() {
64 var cursor = countInterpretation.getAll();
65 while (cursor.move()) {
66 var interval = cursor.getValue();
67 if (!interval.equals(CardinalityIntervals.ONE)) {
68 int nodeId = cursor.getKey().get(0);
69 createVariable(nodeId, interval);
70 activeVariables.add(nodeId);
71 }
72 }
73 }
74
75 private MPVariable createVariable(int nodeId, CardinalityInterval interval) {
76 double lowerBound = interval.lowerBound();
77 double upperBound = getUpperBound(interval);
78 var variable = solver.makeNumVar(lowerBound, upperBound, "x" + nodeId);
79 variables.put(nodeId, variable);
80 return variable;
81 }
82
83 private void countChanged(Tuple key, CardinalityInterval fromValue, CardinalityInterval toValue,
84 boolean ignoredRestoring) {
85 int nodeId = key.get(0);
86 if ((toValue == null || toValue.equals(CardinalityIntervals.ONE))) {
87 if (fromValue != null && !fromValue.equals(CardinalityIntervals.ONE)) {
88 removeActiveVariable(toValue, nodeId);
89 }
90 return;
91 }
92 if (fromValue == null || fromValue.equals(CardinalityIntervals.ONE)) {
93 activeVariables.add(nodeId);
94 }
95 var variable = variables.get(nodeId);
96 if (variable == null) {
97 createVariable(nodeId, toValue);
98 markAsChanged();
99 return;
100 }
101 double lowerBound = toValue.lowerBound();
102 double upperBound = getUpperBound(toValue);
103 if (variable.lb() != lowerBound) {
104 variable.setLb(lowerBound);
105 markAsChanged();
106 }
107 if (variable.ub() != upperBound) {
108 variable.setUb(upperBound);
109 markAsChanged();
110 }
111 }
112
113 private void removeActiveVariable(CardinalityInterval toValue, int nodeId) {
114 var variable = variables.get(nodeId);
115 if (variable == null || !activeVariables.remove(nodeId)) {
116 throw new AssertionError("Variable not active: " + nodeId);
117 }
118 if (toValue == null) {
119 variable.setBounds(0, 0);
120 } else {
121 // Until queries are flushed and the constraints can be properly updated,
122 // the variable corresponding to the (previous) multi-object has to stand in for a single object.
123 variable.setBounds(1, 1);
124 }
125 markAsChanged();
126 }
127
128 MPConstraint makeConstraint() {
129 return solver.makeConstraint();
130 }
131
132 MPVariable getVariable(int nodeId) {
133 var variable = variables.get(nodeId);
134 if (variable != null) {
135 return variable;
136 }
137 var interval = countInterpretation.get(Tuple.of(nodeId));
138 if (interval == null || interval.equals(CardinalityIntervals.ONE)) {
139 interval = CardinalityIntervals.NONE;
140 } else {
141 activeVariables.add(nodeId);
142 markAsChanged();
143 }
144 return createVariable(nodeId, interval);
145 }
146
147 void markAsChanged() {
148 changed = true;
149 }
150
151 @Override
152 public RefinementResult propagate() {
153 var result = RefinementResult.UNCHANGED;
154 RefinementResult currentRoundResult;
155 do {
156 currentRoundResult = propagateOne();
157 result = result.andThen(currentRoundResult);
158 if (result.isRejected()) {
159 return result;
160 }
161 } while (currentRoundResult != RefinementResult.UNCHANGED);
162 return result;
163 }
164
165 private RefinementResult propagateOne() {
166 queryEngine.flushChanges();
167 if (!changed) {
168 return RefinementResult.UNCHANGED;
169 }
170 changed = false;
171 for (var propagator : propagators) {
172 propagator.updateBounds();
173 }
174 var result = RefinementResult.UNCHANGED;
175 if (activeVariables.isEmpty()) {
176 return checkEmptiness();
177 }
178 var iterator = activeVariables.intIterator();
179 while (iterator.hasNext()) {
180 int nodeId = iterator.next();
181 var variable = variables.get(nodeId);
182 if (variable == null) {
183 throw new AssertionError("Missing active variable: " + nodeId);
184 }
185 result = result.andThen(propagateNode(nodeId, variable));
186 if (result.isRejected()) {
187 return result;
188 }
189 }
190 return result;
191 }
192
193 private RefinementResult checkEmptiness() {
194 var emptinessCheckingResult = solver.solve();
195 return switch (emptinessCheckingResult) {
196 case OPTIMAL, UNBOUNDED -> RefinementResult.UNCHANGED;
197 case INFEASIBLE -> RefinementResult.REJECTED;
198 default -> throw new IllegalStateException("Failed to check for consistency: " + emptinessCheckingResult);
199 };
200 }
201
202 private RefinementResult propagateNode(int nodeId, MPVariable variable) {
203 objective.setCoefficient(variable, 1);
204 try {
205 objective.setMinimization();
206 var minimizationResult = solver.solve();
207 int lowerBound;
208 switch (minimizationResult) {
209 case OPTIMAL -> lowerBound = RoundingUtil.roundUp(objective.value());
210 case UNBOUNDED -> lowerBound = 0;
211 case INFEASIBLE -> {
212 return RefinementResult.REJECTED;
213 }
214 default -> throw new IllegalStateException("Failed to solve for minimum of %s: %s"
215 .formatted(variable, minimizationResult));
216 }
217
218 objective.setMaximization();
219 var maximizationResult = solver.solve();
220 UpperCardinality upperBound;
221 switch (maximizationResult) {
222 case OPTIMAL -> upperBound = UpperCardinalities.atMost(RoundingUtil.roundDown(objective.value()));
223 // Problem was feasible when minimizing, the only possible source of {@code UNBOUNDED_OR_INFEASIBLE} is
224 // an unbounded maximization problem. See https://github.com/google/or-tools/issues/3319
225 case UNBOUNDED, INFEASIBLE -> upperBound = UpperCardinalities.UNBOUNDED;
226 default -> throw new IllegalStateException("Failed to solve for maximum of %s: %s"
227 .formatted(variable, minimizationResult));
228 }
229
230 var newInterval = CardinalityIntervals.between(lowerBound, upperBound);
231 var oldInterval = countInterpretation.put(Tuple.of(nodeId), newInterval);
232 if (newInterval.lowerBound() < oldInterval.lowerBound() ||
233 newInterval.upperBound().compareTo(oldInterval.upperBound()) > 0) {
234 throw new IllegalArgumentException("Failed to refine multiplicity %s of node %d to %s"
235 .formatted(oldInterval, nodeId, newInterval));
236 }
237 return newInterval.equals(oldInterval) ? RefinementResult.UNCHANGED : RefinementResult.REFINED;
238 } finally {
239 objective.setCoefficient(variable, 0);
240 }
241 }
242
243 private static double getUpperBound(CardinalityInterval interval) {
244 var upperBound = interval.upperBound();
245 if (upperBound instanceof FiniteUpperCardinality finiteUpperCardinality) {
246 return finiteUpperCardinality.finiteUpperBound();
247 } else if (upperBound instanceof UnboundedUpperCardinality) {
248 return Double.POSITIVE_INFINITY;
249 } else {
250 throw new IllegalArgumentException("Unknown upper bound: " + upperBound);
251 }
252 }
253}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java
new file mode 100644
index 00000000..531a7440
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorBuilderImpl.java
@@ -0,0 +1,86 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import com.google.ortools.Loader;
9import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.model.ModelStoreBuilder;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.scope.ScopePropagatorBuilder;
14import tools.refinery.store.reasoning.scope.ScopePropagatorStoreAdapter;
15import tools.refinery.store.reasoning.translator.TranslationException;
16import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
17import tools.refinery.store.representation.Symbol;
18import tools.refinery.store.representation.cardinality.CardinalityInterval;
19import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
20
21import java.util.*;
22
23public class ScopePropagatorBuilderImpl extends AbstractModelAdapterBuilder<ScopePropagatorStoreAdapter>
24 implements ScopePropagatorBuilder {
25 private Symbol<CardinalityInterval> countSymbol = MultiObjectTranslator.COUNT_STORAGE;
26 private final Map<PartialRelation, CardinalityInterval> scopes = new LinkedHashMap<>();
27 private List<TypeScopePropagator.Factory> typeScopePropagatorFactories;
28
29 @Override
30 public ScopePropagatorBuilder countSymbol(Symbol<CardinalityInterval> countSymbol) {
31 if (countSymbol.arity() != 1) {
32 throw new IllegalArgumentException("Count symbol must have arty 1, got %s with arity %d instead"
33 .formatted(countSymbol, countSymbol.arity()));
34 }
35 if (!countSymbol.valueType().equals(CardinalityInterval.class)) {
36 throw new IllegalArgumentException("Count symbol must have CardinalityInterval values");
37 }
38 if (countSymbol.defaultValue() != null) {
39 throw new IllegalArgumentException("Count symbol must default value null");
40 }
41 this.countSymbol = countSymbol;
42 return this;
43 }
44
45 @Override
46 public ScopePropagatorBuilder scope(PartialRelation type, CardinalityInterval interval) {
47 if (type.arity() != 1) {
48 throw new TranslationException(type, "Only types with arity 1 may have scopes, got %s with arity %d"
49 .formatted(type, type.arity()));
50 }
51 var newValue = scopes.compute(type, (ignoredKey, oldValue) ->
52 oldValue == null ? interval : oldValue.meet(interval));
53 if (newValue.isEmpty()) {
54 throw new TranslationException(type, "Unsatisfiable scope for type %s".formatted(type));
55 }
56 return this;
57 }
58
59 @Override
60 protected void doConfigure(ModelStoreBuilder storeBuilder) {
61 typeScopePropagatorFactories = new ArrayList<>(scopes.size());
62 for (var entry : scopes.entrySet()) {
63 var type = entry.getKey();
64 var bounds = entry.getValue();
65 if (bounds.lowerBound() > 0) {
66 var lowerFactory = new LowerTypeScopePropagator.Factory(type, bounds.lowerBound());
67 typeScopePropagatorFactories.add(lowerFactory);
68 }
69 if (bounds.upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) {
70 var upperFactory = new UpperTypeScopePropagator.Factory(type,
71 finiteUpperCardinality.finiteUpperBound());
72 typeScopePropagatorFactories.add(upperFactory);
73 }
74 }
75 for (var factory : typeScopePropagatorFactories) {
76 factory.configure(storeBuilder);
77 }
78 }
79
80 @Override
81 protected ScopePropagatorStoreAdapter doBuild(ModelStore store) {
82 Loader.loadNativeLibraries();
83 return new ScopePropagatorStoreAdapterImpl(store, countSymbol, Collections.unmodifiableMap(scopes),
84 Collections.unmodifiableList(typeScopePropagatorFactories));
85 }
86}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorStoreAdapterImpl.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorStoreAdapterImpl.java
new file mode 100644
index 00000000..282ffe6f
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/ScopePropagatorStoreAdapterImpl.java
@@ -0,0 +1,58 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.scope.ScopePropagatorStoreAdapter;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.cardinality.CardinalityInterval;
15
16import java.util.List;
17import java.util.Map;
18
19// Not a record, because we want to control getter visibility.
20@SuppressWarnings("ClassCanBeRecord")
21class ScopePropagatorStoreAdapterImpl implements ScopePropagatorStoreAdapter {
22 private final ModelStore store;
23 private final Symbol<CardinalityInterval> countSymbol;
24 private final Map<PartialRelation, CardinalityInterval> scopes;
25 private final List<TypeScopePropagator.Factory> propagatorFactories;
26
27 public ScopePropagatorStoreAdapterImpl(
28 ModelStore store, Symbol<CardinalityInterval> countSymbol,
29 Map<PartialRelation, CardinalityInterval> scopes, List<TypeScopePropagator.Factory> propagatorFactories) {
30 this.store = store;
31 this.countSymbol = countSymbol;
32 this.scopes = scopes;
33 this.propagatorFactories = propagatorFactories;
34 }
35
36 @Override
37 public ModelStore getStore() {
38 return store;
39 }
40
41 Symbol<CardinalityInterval> getCountSymbol() {
42 return countSymbol;
43 }
44
45 @Override
46 public Map<PartialRelation, CardinalityInterval> getScopes() {
47 return scopes;
48 }
49
50 public List<TypeScopePropagator.Factory> getPropagatorFactories() {
51 return propagatorFactories;
52 }
53
54 @Override
55 public ModelAdapter createModelAdapter(Model model) {
56 return new ScopePropagatorAdapterImpl(model, this);
57 }
58}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/TypeScopePropagator.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/TypeScopePropagator.java
new file mode 100644
index 00000000..cfb95829
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/TypeScopePropagator.java
@@ -0,0 +1,67 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import com.google.ortools.linearsolver.MPConstraint;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.resultset.ResultSet;
15import tools.refinery.store.tuple.Tuple;
16
17import java.util.Collection;
18
19abstract class TypeScopePropagator {
20 private final ScopePropagatorAdapterImpl adapter;
21 private final ResultSet<Boolean> allNodes;
22 private final ResultSet<Boolean> multiNodes;
23 protected final MPConstraint constraint;
24
25 protected TypeScopePropagator(ScopePropagatorAdapterImpl adapter, RelationalQuery allQuery,
26 RelationalQuery multiQuery) {
27 this.adapter = adapter;
28 var queryEngine = adapter.getModel().getAdapter(ModelQueryAdapter.class);
29 allNodes = queryEngine.getResultSet(allQuery);
30 multiNodes = queryEngine.getResultSet(multiQuery);
31 constraint = adapter.makeConstraint();
32 constraint.setBounds(0, Double.POSITIVE_INFINITY);
33 var cursor = multiNodes.getAll();
34 while (cursor.move()) {
35 var variable = adapter.getVariable(cursor.getKey().get(0));
36 constraint.setCoefficient(variable, 1);
37 }
38 allNodes.addListener(this::allChanged);
39 multiNodes.addListener(this::multiChanged);
40 }
41
42 public abstract void updateBounds();
43
44 protected int getSingleCount() {
45 return allNodes.size() - multiNodes.size();
46 }
47
48 private void allChanged(Tuple ignoredKey, Boolean ignoredOldValue, Boolean ignoredNewValue) {
49 adapter.markAsChanged();
50 }
51
52 private void multiChanged(Tuple key, Boolean ignoredOldValue, Boolean newValue) {
53 var variable = adapter.getVariable(key.get(0));
54 constraint.setCoefficient(variable, Boolean.TRUE.equals(newValue) ? 1 : 0);
55 adapter.markAsChanged();
56 }
57
58 public abstract static class Factory {
59 public abstract TypeScopePropagator createPropagator(ScopePropagatorAdapterImpl adapter);
60
61 protected abstract Collection<AnyQuery> getQueries();
62
63 public void configure(ModelStoreBuilder storeBuilder) {
64 storeBuilder.getAdapter(ModelQueryBuilder.class).queries(getQueries());
65 }
66 }
67}
diff --git a/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/UpperTypeScopePropagator.java b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/UpperTypeScopePropagator.java
new file mode 100644
index 00000000..a0be0fb4
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/main/java/tools/refinery/store/reasoning/scope/internal/UpperTypeScopePropagator.java
@@ -0,0 +1,59 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.dnf.Query;
10import tools.refinery.store.query.dnf.RelationalQuery;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12
13import java.util.Collection;
14import java.util.List;
15
16import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
17import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
18
19class UpperTypeScopePropagator extends TypeScopePropagator {
20 private final int upperBound;
21
22 private UpperTypeScopePropagator(ScopePropagatorAdapterImpl adapter, int upperBound, RelationalQuery allQuery,
23 RelationalQuery multiQuery) {
24 super(adapter, allQuery, multiQuery);
25 this.upperBound = upperBound;
26 }
27
28 @Override
29 public void updateBounds() {
30 constraint.setUb(upperBound - getSingleCount());
31 }
32
33 public static class Factory extends TypeScopePropagator.Factory {
34 private final int upperBound;
35 private final RelationalQuery allMust;
36 private final RelationalQuery multiMust;
37
38 public Factory(PartialRelation type, int upperBound) {
39 this.upperBound = upperBound;
40 allMust = Query.of(type.name() + "#must", (builder, instance) -> builder.clause(
41 must(type.call(instance))
42 ));
43 multiMust = Query.of(type.name() + "#multiMust", (builder, instance) -> builder.clause(
44 must(type.call(instance)),
45 MULTI_VIEW.call(instance)
46 ));
47 }
48
49 @Override
50 public TypeScopePropagator createPropagator(ScopePropagatorAdapterImpl adapter) {
51 return new UpperTypeScopePropagator(adapter, upperBound, allMust, multiMust);
52 }
53
54 @Override
55 protected Collection<AnyQuery> getQueries() {
56 return List.of(allMust, multiMust);
57 }
58 }
59}
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java
new file mode 100644
index 00000000..95c4ac68
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MPSolverTest.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope;
7
8import com.google.ortools.Loader;
9import com.google.ortools.linearsolver.MPSolver;
10import org.junit.jupiter.api.BeforeAll;
11import org.junit.jupiter.api.Test;
12
13import static org.hamcrest.MatcherAssert.assertThat;
14import static org.hamcrest.Matchers.closeTo;
15import static org.hamcrest.Matchers.is;
16
17class MPSolverTest {
18 @BeforeAll
19 static void beforeAll() {
20 Loader.loadNativeLibraries();
21 }
22
23 @Test
24 void updateProblemTest() {
25 var solver = MPSolver.createSolver("GLOP");
26 var x = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "x");
27 var y = solver.makeNumVar(0, 1, "y");
28 var constraint = solver.makeConstraint(5, 5);
29 constraint.setCoefficient(x, 1);
30 constraint.setCoefficient(y, 1);
31 var objective = solver.objective();
32
33 objective.setCoefficient(x, 1);
34 objective.setMinimization();
35 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
36 assertThat(objective.value(), closeTo(4, 0.01));
37
38 objective.setMaximization();
39 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
40 assertThat(objective.value(), closeTo(5, 0.01));
41
42 objective.setCoefficient(x, 0);
43 objective.setCoefficient(y, 1);
44 objective.setMinimization();
45 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
46 assertThat(objective.value(), closeTo(0, 0.01));
47
48 objective.setMaximization();
49 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
50 assertThat(objective.value(), closeTo(1, 0.01));
51 }
52
53 @Test
54 void unboundedIsInfeasibleTest() {
55 var solver = MPSolver.createSolver("GLOP");
56 var x = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "x");
57 var objective = solver.objective();
58 objective.setCoefficient(x, 1);
59
60 objective.setMinimization();
61 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
62 assertThat(objective.value(), closeTo(0, 0.01));
63
64 objective.setMaximization();
65 assertThat(solver.solve(), is(MPSolver.ResultStatus.INFEASIBLE));
66 }
67
68 @Test
69 void constantTest() {
70 var solver = MPSolver.createSolver("GLOP");
71 var x = solver.makeNumVar(1, 1, "x");
72 var objective = solver.objective();
73 objective.setCoefficient(x, 1);
74
75 objective.setMinimization();
76 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
77 assertThat(objective.value(), closeTo(1, 0.01));
78
79 objective.setMaximization();
80 assertThat(solver.solve(), is(MPSolver.ResultStatus.OPTIMAL));
81 assertThat(objective.value(), closeTo(1, 0.01));
82 }
83}
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MultiObjectTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MultiObjectTest.java
new file mode 100644
index 00000000..42ce2f56
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/MultiObjectTest.java
@@ -0,0 +1,206 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.model.Model;
12import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.ReasoningStoreAdapter;
16import tools.refinery.store.reasoning.refinement.RefinementResult;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.seed.ModelSeed;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.TruthValue;
23import tools.refinery.store.representation.cardinality.CardinalityInterval;
24import tools.refinery.store.representation.cardinality.CardinalityIntervals;
25import tools.refinery.store.tuple.Tuple;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29
30class MultiObjectTest {
31 private static final PartialRelation person = new PartialRelation("Person", 1);
32
33 private ModelStore store;
34 private Model model;
35 private Interpretation<CardinalityInterval> countStorage;
36
37 @BeforeEach
38 void beforeEach() {
39 store = ModelStore.builder()
40 .with(ViatraModelQueryAdapter.builder())
41 .with(ReasoningAdapter.builder())
42 .with(new MultiObjectTranslator())
43 .with(PartialRelationTranslator.of(person)
44 .symbol(Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE)))
45 .with(ScopePropagatorAdapter.builder()
46 .scope(person, CardinalityIntervals.between(5, 15)))
47 .build();
48 model = null;
49 countStorage = null;
50 }
51
52 @Test
53 void oneMultiObjectSatisfiableTest() {
54 createModel(ModelSeed.builder(4)
55 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
56 .reducedValue(CardinalityIntervals.ONE)
57 .put(Tuple.of(0), CardinalityIntervals.SET))
58 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
59 .build());
60 assertThat(propagate(), is(RefinementResult.REFINED));
61 assertThat(countStorage.get(Tuple.of(0)), is(CardinalityIntervals.between(2, 12)));
62 }
63
64 @Test
65 void oneMultiObjectExistingBoundSatisfiableTest() {
66 createModel(ModelSeed.builder(4)
67 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
68 .reducedValue(CardinalityIntervals.ONE)
69 .put(Tuple.of(0), CardinalityIntervals.between(5, 20)))
70 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
71 .build());
72 assertThat(propagate(), is(RefinementResult.REFINED));
73 assertThat(countStorage.get(Tuple.of(0)), is(CardinalityIntervals.between(5, 12)));
74 }
75
76 @Test
77 void oneMultiObjectUnsatisfiableUpperTest() {
78 createModel(ModelSeed.builder(21)
79 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
80 .reducedValue(CardinalityIntervals.ONE)
81 .put(Tuple.of(0), CardinalityIntervals.SET))
82 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
83 .build());
84 assertThat(propagate(), is(RefinementResult.REJECTED));
85 }
86
87 @Test
88 void noMultiObjectSatisfiableTest() {
89 createModel(ModelSeed.builder(10)
90 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE))
91 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
92 .build());
93 assertThat(propagate(), is(RefinementResult.UNCHANGED));
94 }
95
96 @Test
97 void noMultiObjectUnsatisfiableTest() {
98 createModel(ModelSeed.builder(2)
99 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE))
100 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
101 .build());
102 assertThat(propagate(), is(RefinementResult.REJECTED));
103 }
104
105 @Test
106 void oneMultiObjectExistingBoundUnsatisfiableLowerTest() {
107 createModel(ModelSeed.builder(4)
108 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
109 .reducedValue(CardinalityIntervals.ONE)
110 .put(Tuple.of(0), CardinalityIntervals.atLeast(20)))
111 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
112 .build());
113 assertThat(propagate(), is(RefinementResult.REJECTED));
114 }
115
116 @Test
117 void oneMultiObjectExistingBoundUnsatisfiableUpperTest() {
118 createModel(ModelSeed.builder(4)
119 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
120 .reducedValue(CardinalityIntervals.ONE)
121 .put(Tuple.of(0), CardinalityIntervals.atMost(1)))
122 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
123 .build());
124 assertThat(propagate(), is(RefinementResult.REJECTED));
125 }
126
127 @Test
128 void twoMultiObjectsSatisfiableTest() {
129 createModel(ModelSeed.builder(5)
130 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
131 .reducedValue(CardinalityIntervals.ONE)
132 .put(Tuple.of(0), CardinalityIntervals.SET)
133 .put(Tuple.of(1), CardinalityIntervals.SET))
134 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
135 .build());
136 assertThat(propagate(), is(RefinementResult.REFINED));
137 assertThat(countStorage.get(Tuple.of(0)), is(CardinalityIntervals.atMost(12)));
138 assertThat(countStorage.get(Tuple.of(1)), is(CardinalityIntervals.atMost(12)));
139 }
140
141 @Test
142 void twoMultiObjectsExistingBoundSatisfiableTest() {
143 createModel(ModelSeed.builder(5)
144 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
145 .reducedValue(CardinalityIntervals.ONE)
146 .put(Tuple.of(0), CardinalityIntervals.between(7, 20))
147 .put(Tuple.of(1), CardinalityIntervals.atMost(11)))
148 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
149 .build());
150 assertThat(propagate(), is(RefinementResult.REFINED));
151 assertThat(countStorage.get(Tuple.of(0)), is(CardinalityIntervals.between(7, 12)));
152 assertThat(countStorage.get(Tuple.of(1)), is(CardinalityIntervals.atMost(5)));
153 }
154
155 @Test
156 void twoMultiObjectsExistingBoundUnsatisfiableUpperTest() {
157 createModel(ModelSeed.builder(5)
158 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
159 .reducedValue(CardinalityIntervals.ONE)
160 .put(Tuple.of(0), CardinalityIntervals.between(7, 20))
161 .put(Tuple.of(1), CardinalityIntervals.exactly(11)))
162 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
163 .build());
164 assertThat(propagate(), is(RefinementResult.REJECTED));
165 }
166
167 @Test
168 void twoMultiObjectsExistingBoundUnsatisfiableLowerTest() {
169 createModel(ModelSeed.builder(3)
170 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
171 .reducedValue(CardinalityIntervals.ONE)
172 .put(Tuple.of(0), CardinalityIntervals.LONE)
173 .put(Tuple.of(1), CardinalityIntervals.atMost(2)))
174 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
175 .build());
176 assertThat(propagate(), is(RefinementResult.REJECTED));
177 }
178
179 @Test
180 void multiToSingleTest() {
181 createModel(ModelSeed.builder(5)
182 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
183 .reducedValue(CardinalityIntervals.ONE)
184 .put(Tuple.of(0), CardinalityIntervals.LONE)
185 .put(Tuple.of(1), CardinalityIntervals.SET))
186 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
187 .build());
188 assertThat(propagate(), is(RefinementResult.REFINED));
189 assertThat(countStorage.get(Tuple.of(0)), is(CardinalityIntervals.LONE));
190 assertThat(countStorage.get(Tuple.of(1)), is(CardinalityIntervals.between(1, 12)));
191 countStorage.put(Tuple.of(0), CardinalityIntervals.ONE);
192 assertThat(propagate(), is(RefinementResult.REFINED));
193 assertThat(countStorage.get(Tuple.of(1)), is(CardinalityIntervals.between(1, 11)));
194 countStorage.put(Tuple.of(1), CardinalityIntervals.ONE);
195 assertThat(propagate(), is(RefinementResult.UNCHANGED));
196 }
197
198 private void createModel(ModelSeed modelSeed) {
199 model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
200 countStorage = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
201 }
202
203 private RefinementResult propagate() {
204 return model.getAdapter(ScopePropagatorAdapter.class).propagate();
205 }
206}
diff --git a/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/internal/RoundingUtilTest.java b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/internal/RoundingUtilTest.java
new file mode 100644
index 00000000..9daed660
--- /dev/null
+++ b/subprojects/store-reasoning-scope/src/test/java/tools/refinery/store/reasoning/scope/internal/RoundingUtilTest.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.scope.internal;
7
8import org.junit.jupiter.params.ParameterizedTest;
9import org.junit.jupiter.params.provider.Arguments;
10import org.junit.jupiter.params.provider.MethodSource;
11
12import java.util.stream.Stream;
13
14import static org.hamcrest.MatcherAssert.assertThat;
15import static org.hamcrest.Matchers.is;
16
17class RoundingUtilTest {
18 @ParameterizedTest
19 @MethodSource
20 void roundUpTest(double value, int expected) {
21 int actual = RoundingUtil.roundUp(value);
22 assertThat(actual, is(expected));
23 }
24
25 static Stream<Arguments> roundUpTest() {
26 return Stream.of(
27 Arguments.of(0.0, 0),
28 Arguments.of(-0.0, 0),
29 Arguments.of(-0.9, 0),
30 Arguments.of(-2, 0),
31 Arguments.of(0.009, 0),
32 Arguments.of(0.011, 1),
33 Arguments.of(0.1, 1),
34 Arguments.of(0.991, 1),
35 Arguments.of(1, 1),
36 Arguments.of(1.009, 1),
37 Arguments.of(1.011, 2),
38 Arguments.of(1.5, 2),
39 Arguments.of(2, 2),
40 Arguments.of(100.5, 101)
41 );
42 }
43
44 @ParameterizedTest
45 @MethodSource
46 void roundDownTest(double value, int expected) {
47 int actual = RoundingUtil.roundDown(value);
48 assertThat(actual, is(expected));
49 }
50
51 static Stream<Arguments> roundDownTest() {
52 return Stream.of(
53 Arguments.of(0.0, 0),
54 Arguments.of(-0.0, 0),
55 Arguments.of(-0.9, 0),
56 Arguments.of(-2, 0),
57 Arguments.of(0.989, 0),
58 Arguments.of(0.991, 1),
59 Arguments.of(1, 1),
60 Arguments.of(1.5, 1),
61 Arguments.of(1.009, 1),
62 Arguments.of(1.989, 1),
63 Arguments.of(1.991, 2),
64 Arguments.of(2, 2),
65 Arguments.of(2.009, 2),
66 Arguments.of(100.5, 100)
67 );
68 }
69}
diff --git a/subprojects/store-reasoning/build.gradle.kts b/subprojects/store-reasoning/build.gradle.kts
index abad0491..ed8355f3 100644
--- a/subprojects/store-reasoning/build.gradle.kts
+++ b/subprojects/store-reasoning/build.gradle.kts
@@ -9,5 +9,7 @@ plugins {
9} 9}
10 10
11dependencies { 11dependencies {
12 api(project(":refinery-store-query")) 12 api(project(":refinery-store-dse"))
13 testImplementation(testFixtures(project(":refinery-store-query")))
14 testImplementation(project(":refinery-store-query-viatra"))
13} 15}
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
deleted file mode 100644
index d3a216d8..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java
+++ /dev/null
@@ -1,20 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning;
7
8public enum MergeResult {
9 UNCHANGED,
10 REFINED,
11 REJECTED;
12
13 public MergeResult andAlso(MergeResult other) {
14 return switch (this) {
15 case UNCHANGED -> other;
16 case REFINED -> other == REJECTED ? REJECTED : REFINED;
17 case REJECTED -> REJECTED;
18 };
19 }
20}
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
deleted file mode 100644
index 4140d640..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java
+++ /dev/null
@@ -1,25 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.reasoning.representation.PartialSymbol;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation {
13 @Override
14 PartialSymbol<A, C> getPartialSymbol();
15
16 A get(Tuple key);
17
18 Cursor<Tuple, A> getAll();
19
20 MergeResult merge(Tuple key, A value);
21
22 C getConcrete(Tuple key);
23
24 Cursor<Tuple, C> getAllConcrete();
25}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java
index 6d5d6f89..1dda7ac1 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java
@@ -5,26 +5,49 @@
5 */ 5 */
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning;
7 7
8import org.jetbrains.annotations.Nullable;
8import tools.refinery.store.adapter.ModelAdapter; 9import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.resultset.ResultSet; 10import tools.refinery.store.reasoning.internal.ReasoningBuilderImpl;
10import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation;
12import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Concreteness;
14import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner;
15import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 16import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.reasoning.representation.PartialRelation; 17import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.representation.PartialSymbol; 18import tools.refinery.store.reasoning.representation.PartialSymbol;
19import tools.refinery.store.tuple.Tuple1;
14 20
15public interface ReasoningAdapter extends ModelAdapter { 21public interface ReasoningAdapter extends ModelAdapter {
16 PartialRelation EXISTS = new PartialRelation("exists", 1); 22 PartialRelation EXISTS_SYMBOL = PartialSymbol.of("exists", 1);
23 PartialRelation EQUALS_SYMBOL = PartialSymbol.of("equals", 2);
17 24
18 @Override 25 @Override
19 ReasoningStoreAdapter getStoreAdapter(); 26 ReasoningStoreAdapter getStoreAdapter();
20 27
21 default AnyPartialInterpretation getPartialInterpretation(AnyPartialSymbol partialSymbol) { 28 default AnyPartialInterpretation getPartialInterpretation(Concreteness concreteness,
22 // Cast to disambiguate overloads. 29 AnyPartialSymbol partialSymbol) {
23 var typedPartialSymbol = (PartialSymbol<?, ?>) partialSymbol; 30 return getPartialInterpretation(concreteness, (PartialSymbol<?, ?>) partialSymbol);
24 return getPartialInterpretation(typedPartialSymbol);
25 } 31 }
26 32
27 <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol); 33 <A, C> PartialInterpretation<A, C> getPartialInterpretation(Concreteness concreteness,
34 PartialSymbol<A, C> partialSymbol);
28 35
29 ResultSet<Boolean> getLiftedResultSet(Dnf query); 36 default AnyPartialInterpretationRefiner getRefiner(AnyPartialSymbol partialSymbol) {
37 return getRefiner((PartialSymbol<?, ?>) partialSymbol);
38 }
39
40 <A, C> PartialInterpretationRefiner<A, C> getRefiner(PartialSymbol<A, C> partialSymbol);
41
42 @Nullable
43 Tuple1 split(int parentMultiObject);
44
45 @Nullable
46 Tuple1 focus(int parentObject);
47
48 boolean cleanup(int nodeToDelete);
49
50 static ReasoningBuilder builder() {
51 return new ReasoningBuilderImpl();
52 }
30} 53}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java
index d3a337e8..79bce33e 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java
@@ -6,27 +6,54 @@
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning;
7 7
8import tools.refinery.store.adapter.ModelAdapterBuilder; 8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.dse.transition.objectives.Objective;
9import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.reasoning.literal.Modality;
11import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.FunctionalQuery;
13import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
18import tools.refinery.store.reasoning.refinement.StorageRefiner;
19import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator;
20import tools.refinery.store.representation.Symbol;
12 21
13import java.util.Collection; 22import java.util.Collection;
14import java.util.List; 23import java.util.List;
15 24
16@SuppressWarnings("UnusedReturnValue") 25@SuppressWarnings("UnusedReturnValue")
17public interface ReasoningBuilder extends ModelAdapterBuilder { 26public interface ReasoningBuilder extends ModelAdapterBuilder {
18 default ReasoningBuilder liftedQueries(Dnf... liftedQueries) { 27 ReasoningBuilder requiredInterpretations(Collection<Concreteness> requiredInterpretations);
19 return liftedQueries(List.of(liftedQueries)); 28
29 default ReasoningBuilder requiredInterpretations(Concreteness... requiredInterpretations) {
30 return requiredInterpretations(List.of(requiredInterpretations));
20 } 31 }
21 32
22 default ReasoningBuilder liftedQueries(Collection<Dnf> liftedQueries) { 33 ReasoningBuilder partialSymbol(AnyPartialSymbolTranslator translator);
23 liftedQueries.forEach(this::liftedQuery); 34
35 <T> ReasoningBuilder storageRefiner(Symbol<T> symbol, StorageRefiner.Factory<T> refiner);
36
37 ReasoningBuilder initializer(PartialModelInitializer initializer);
38
39 ReasoningBuilder objective(Objective objective);
40
41 default ReasoningBuilder objectives(Objective... objectives) {
42 return objectives(List.of(objectives));
43 }
44
45 default ReasoningBuilder objectives(Collection<Objective> objectives) {
46 objectives.forEach(this::objective);
24 return this; 47 return this;
25 } 48 }
26 49
27 ReasoningBuilder liftedQuery(Dnf liftedQuery); 50 <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query);
51
52 RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query);
53
54 <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query);
28 55
29 Dnf lift(Modality modality, Dnf query); 56 Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf);
30 57
31 @Override 58 @Override
32 ReasoningStoreAdapter build(ModelStore store); 59 ReasoningStoreAdapter build(ModelStore store);
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java
index c9795255..fe3cc3ea 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java
@@ -7,15 +7,21 @@ package tools.refinery.store.reasoning;
7 7
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.literal.Concreteness;
10import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 11import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11import tools.refinery.store.query.dnf.Dnf; 12import tools.refinery.store.reasoning.seed.ModelSeed;
12 13
13import java.util.Collection; 14import java.util.Collection;
15import java.util.Set;
14 16
15public interface ReasoningStoreAdapter extends ModelStoreAdapter { 17public interface ReasoningStoreAdapter extends ModelStoreAdapter {
16 Collection<AnyPartialSymbol> getPartialSymbols(); 18 Collection<AnyPartialSymbol> getPartialSymbols();
17 19
18 Collection<Dnf> getLiftedQueries(); 20 Collection<AnyPartialSymbol> getRefinablePartialSymbols();
21
22 Set<Concreteness> getSupportedInterpretations();
23
24 Model createInitialModel(ModelSeed modelSeed);
19 25
20 @Override 26 @Override
21 ReasoningAdapter createModelAdapter(Model model); 27 ReasoningAdapter createModelAdapter(Model model);
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java
new file mode 100644
index 00000000..5a6f22d2
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java
@@ -0,0 +1,48 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
9import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13
14import java.util.List;
15
16public class FocusActionLiteral extends AbstractActionLiteral {
17 private final NodeVariable parentNode;
18 private final NodeVariable childNode;
19
20 public FocusActionLiteral(NodeVariable parentNode, NodeVariable childNode) {
21 this.parentNode = parentNode;
22 this.childNode = childNode;
23 }
24
25 public NodeVariable getParentNode() {
26 return parentNode;
27 }
28
29 public NodeVariable getChildNode() {
30 return childNode;
31 }
32
33 @Override
34 public List<NodeVariable> getInputVariables() {
35 return List.of(parentNode);
36 }
37
38 @Override
39 public List<NodeVariable> getOutputVariables() {
40 return List.of(childNode);
41 }
42
43 @Override
44 public BoundActionLiteral bindToModel(Model model) {
45 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
46 return tuple -> reasoningAdapter.focus(tuple.get(0));
47 }
48}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java
new file mode 100644
index 00000000..8d0d7961
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java
@@ -0,0 +1,60 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
9import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.representation.PartialSymbol;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.List;
17
18public class MergeActionLiteral<A, C> extends AbstractActionLiteral {
19 private final PartialSymbol<A, C> partialSymbol;
20 private final List<NodeVariable> parameters;
21 private final A value;
22
23 public MergeActionLiteral(PartialSymbol<A, C> partialSymbol, A value, List<NodeVariable> parameters) {
24 if (partialSymbol.arity() != parameters.size()) {
25 throw new IllegalArgumentException("Expected %d parameters for partial symbol %s, got %d instead"
26 .formatted(partialSymbol.arity(), partialSymbol, parameters.size()));
27 }
28 this.partialSymbol = partialSymbol;
29 this.parameters = parameters;
30 this.value = value;
31 }
32
33 public PartialSymbol<A, C> getPartialSymbol() {
34 return partialSymbol;
35 }
36
37 public List<NodeVariable> getParameters() {
38 return parameters;
39 }
40
41 public A getValue() {
42 return value;
43 }
44
45 @Override
46 public List<NodeVariable> getInputVariables() {
47 return getParameters();
48 }
49
50 @Override
51 public List<NodeVariable> getOutputVariables() {
52 return List.of();
53 }
54
55 @Override
56 public BoundActionLiteral bindToModel(Model model) {
57 var refiner = model.getAdapter(ReasoningAdapter.class).getRefiner(partialSymbol);
58 return tuple -> refiner.merge(tuple, value) ? Tuple.of() : null;
59 }
60}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java
new file mode 100644
index 00000000..990d11e5
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.query.term.NodeVariable;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.TruthValue;
12
13import java.util.List;
14
15public final class PartialActionLiterals {
16 private PartialActionLiterals() {
17 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
18 }
19
20 public static <A, C> MergeActionLiteral<A, C> merge(PartialSymbol<A, C> partialSymbol, A value,
21 NodeVariable... parameters) {
22 return new MergeActionLiteral<>(partialSymbol, value, List.of(parameters));
23 }
24
25 public static MergeActionLiteral<TruthValue, Boolean> add(PartialRelation partialRelation,
26 NodeVariable... parameters) {
27 return merge(partialRelation, TruthValue.TRUE, parameters);
28 }
29
30 public static MergeActionLiteral<TruthValue, Boolean> remove(PartialRelation partialRelation,
31 NodeVariable... parameters) {
32 return merge(partialRelation, TruthValue.FALSE, parameters);
33 }
34
35 public static FocusActionLiteral focus(NodeVariable parent, NodeVariable child) {
36 return new FocusActionLiteral(parent, child);
37 }
38}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java
new file mode 100644
index 00000000..40993235
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java
@@ -0,0 +1,223 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.DnfBuilder;
11import tools.refinery.store.query.dnf.DnfClause;
12import tools.refinery.store.query.literal.AbstractCallLiteral;
13import tools.refinery.store.query.literal.AbstractCountLiteral;
14import tools.refinery.store.query.literal.CallPolarity;
15import tools.refinery.store.query.literal.Literal;
16import tools.refinery.store.query.term.Aggregator;
17import tools.refinery.store.query.term.ConstantTerm;
18import tools.refinery.store.query.term.Term;
19import tools.refinery.store.query.term.Variable;
20import tools.refinery.store.query.term.int_.IntTerms;
21import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
22import tools.refinery.store.reasoning.ReasoningAdapter;
23import tools.refinery.store.reasoning.literal.*;
24import tools.refinery.store.reasoning.representation.PartialRelation;
25import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
26import tools.refinery.store.representation.cardinality.UpperCardinalities;
27
28import java.util.*;
29import java.util.function.BinaryOperator;
30
31class PartialClauseRewriter {
32 private final PartialQueryRewriter rewriter;
33 private final List<Literal> completedLiterals = new ArrayList<>();
34 private final Deque<Literal> workList = new ArrayDeque<>();
35 private final Set<Variable> positiveVariables = new LinkedHashSet<>();
36 private final Set<Variable> unmodifiablePositiveVariables = Collections.unmodifiableSet(positiveVariables);
37
38 public PartialClauseRewriter(PartialQueryRewriter rewriter) {
39 this.rewriter = rewriter;
40 }
41
42 public List<Literal> rewriteClause(DnfClause clause) {
43 workList.addAll(clause.literals());
44 while (!workList.isEmpty()) {
45 var literal = workList.removeFirst();
46 rewrite(literal);
47 }
48 return completedLiterals;
49 }
50
51 private void rewrite(Literal literal) {
52 if (!(literal instanceof AbstractCallLiteral callLiteral)) {
53 markAsDone(literal);
54 return;
55 }
56 if (callLiteral instanceof CountLowerBoundLiteral countLowerBoundLiteral) {
57 rewriteCountLowerBound(countLowerBoundLiteral);
58 return;
59 }
60 if (callLiteral instanceof CountUpperBoundLiteral countUpperBoundLiteral) {
61 rewriteCountUpperBound(countUpperBoundLiteral);
62 return;
63 }
64 if (callLiteral instanceof CountCandidateLowerBoundLiteral countCandidateLowerBoundLiteral) {
65 rewriteCountCandidateLowerBound(countCandidateLowerBoundLiteral);
66 return;
67 }
68 if (callLiteral instanceof CountCandidateUpperBoundLiteral countCandidateUpperBoundLiteral) {
69 rewriteCountCandidateUpperBound(countCandidateUpperBoundLiteral);
70 return;
71 }
72 var target = callLiteral.getTarget();
73 if (target instanceof Dnf dnf) {
74 rewriteRecursively(callLiteral, dnf);
75 } else if (target instanceof ModalConstraint modalConstraint) {
76 var modality = modalConstraint.modality();
77 var concreteness = modalConstraint.concreteness();
78 var constraint = modalConstraint.constraint();
79 if (constraint instanceof Dnf dnf) {
80 rewriteRecursively(callLiteral, modality, concreteness, dnf);
81 } else if (constraint instanceof PartialRelation partialRelation) {
82 rewrite(callLiteral, modality, concreteness, partialRelation);
83 } else {
84 throw new IllegalArgumentException("Cannot interpret modal constraint: " + modalConstraint);
85 }
86 } else {
87 markAsDone(literal);
88 }
89 }
90
91 private void rewriteCountLowerBound(CountLowerBoundLiteral literal) {
92 rewritePartialCount(literal, "lower", Modality.MUST, MultiObjectTranslator.LOWER_CARDINALITY_VIEW, 1,
93 IntTerms::mul, IntTerms.INT_SUM);
94 }
95
96 private void rewriteCountUpperBound(CountUpperBoundLiteral literal) {
97 rewritePartialCount(literal, "upper", Modality.MAY, MultiObjectTranslator.UPPER_CARDINALITY_VIEW,
98 UpperCardinalities.ONE, UpperCardinalityTerms::mul, UpperCardinalityTerms.UPPER_CARDINALITY_SUM);
99 }
100
101 private <T> void rewritePartialCount(AbstractCountLiteral<T> literal, String name, Modality modality,
102 Constraint view, T one, BinaryOperator<Term<T>> mul, Aggregator<T, T> sum) {
103 var type = literal.getResultType();
104 var countResult = computeCountVariables(literal, Concreteness.PARTIAL, name);
105 var builder = countResult.builder();
106 var outputVariable = builder.parameter(type);
107 var variablesToCount = countResult.variablesToCount();
108
109 var literals = new ArrayList<Literal>();
110 literals.add(new ModalConstraint(modality, Concreteness.PARTIAL, literal.getTarget())
111 .call(CallPolarity.POSITIVE, countResult.rewrittenArguments()));
112 switch (variablesToCount.size()) {
113 case 0 -> literals.add(outputVariable.assign(new ConstantTerm<>(type, one)));
114 case 1 -> literals.add(view.call(variablesToCount.get(0),
115 outputVariable));
116 default -> {
117 var firstCount = Variable.of(type);
118 literals.add(view.call(variablesToCount.get(0), firstCount));
119 int length = variablesToCount.size();
120 Term<T> accumulator = firstCount;
121 for (int i = 1; i < length; i++) {
122 var countVariable = Variable.of(type);
123 literals.add(view.call(variablesToCount.get(i), countVariable));
124 accumulator = mul.apply(accumulator, countVariable);
125 }
126 literals.add(outputVariable.assign(accumulator));
127 }
128 }
129 builder.clause(literals);
130
131 var helperQuery = builder.build();
132 var aggregationVariable = Variable.of(type);
133 var helperArguments = countResult.helperArguments();
134 helperArguments.add(aggregationVariable);
135 workList.addFirst(literal.getResultVariable().assign(helperQuery.aggregateBy(aggregationVariable, sum,
136 helperArguments)));
137 }
138
139 private void rewriteCountCandidateLowerBound(CountCandidateLowerBoundLiteral literal) {
140 rewriteCandidateCount(literal, "lower", Modality.MAY);
141 }
142
143 private void rewriteCountCandidateUpperBound(CountCandidateUpperBoundLiteral literal) {
144 rewriteCandidateCount(literal, "upper", Modality.MUST);
145 }
146
147 private void rewriteCandidateCount(AbstractCountLiteral<Integer> literal, String name, Modality modality) {
148 var countResult = computeCountVariables(literal, Concreteness.CANDIDATE, name);
149 var builder = countResult.builder();
150
151 var literals = new ArrayList<Literal>();
152 literals.add(new ModalConstraint(modality, Concreteness.CANDIDATE, literal.getTarget())
153 .call(CallPolarity.POSITIVE, countResult.rewrittenArguments()));
154 for (var variable : countResult.variablesToCount()) {
155 literals.add(new ModalConstraint(modality, Concreteness.CANDIDATE, ReasoningAdapter.EXISTS_SYMBOL)
156 .call(variable));
157 }
158 builder.clause(literals);
159
160 var helperQuery = builder.build();
161 workList.addFirst(literal.getResultVariable().assign(helperQuery.count(countResult.helperArguments())));
162 }
163
164 private CountResult computeCountVariables(AbstractCountLiteral<?> literal, Concreteness concreteness,
165 String name) {
166 var target = literal.getTarget();
167 int arity = target.arity();
168 var parameters = target.getParameters();
169 var literalArguments = literal.getArguments();
170 var privateVariables = literal.getPrivateVariables(positiveVariables);
171 var builder = Dnf.builder("%s#%s#%s".formatted(target.name(), concreteness, name));
172 var rewrittenArguments = new ArrayList<Variable>(parameters.size());
173 var variablesToCount = new ArrayList<Variable>();
174 var helperArguments = new ArrayList<Variable>();
175 var literalToRewrittenArgumentMap = new HashMap<Variable, Variable>();
176 for (int i = 0; i < arity; i++) {
177 var literalArgument = literalArguments.get(i);
178 var parameter = parameters.get(i);
179 var rewrittenArgument = literalToRewrittenArgumentMap.computeIfAbsent(literalArgument, key -> {
180 helperArguments.add(key);
181 var newArgument = builder.parameter(parameter);
182 if (privateVariables.contains(key)) {
183 variablesToCount.add(newArgument);
184 }
185 return newArgument;
186 });
187 rewrittenArguments.add(rewrittenArgument);
188 }
189 return new CountResult(builder, rewrittenArguments, helperArguments, variablesToCount);
190 }
191
192 private void markAsDone(Literal literal) {
193 completedLiterals.add(literal);
194 positiveVariables.addAll(literal.getOutputVariables());
195 }
196
197 private void rewriteRecursively(AbstractCallLiteral callLiteral, Modality modality, Concreteness concreteness,
198 Dnf dnf) {
199 var liftedDnf = rewriter.getLifter().lift(modality, concreteness, dnf);
200 rewriteRecursively(callLiteral, liftedDnf);
201 }
202
203 private void rewriteRecursively(AbstractCallLiteral callLiteral, Dnf dnf) {
204 var rewrittenDnf = rewriter.rewrite(dnf);
205 var rewrittenLiteral = callLiteral.withTarget(rewrittenDnf);
206 markAsDone(rewrittenLiteral);
207 }
208
209 private void rewrite(AbstractCallLiteral callLiteral, Modality modality, Concreteness concreteness,
210 PartialRelation partialRelation) {
211 var relationRewriter = rewriter.getRelationRewriter(partialRelation);
212 var literals = relationRewriter.rewriteLiteral(
213 unmodifiablePositiveVariables, callLiteral, modality, concreteness);
214 int length = literals.size();
215 for (int i = length - 1; i >= 0; i--) {
216 workList.addFirst(literals.get(i));
217 }
218 }
219
220 private record CountResult(DnfBuilder builder, List<Variable> rewrittenArguments, List<Variable> helperArguments,
221 List<Variable> variablesToCount) {
222 }
223}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java
new file mode 100644
index 00000000..79cba263
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java
@@ -0,0 +1,53 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.rewriter.AbstractRecursiveRewriter;
10import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
11import tools.refinery.store.reasoning.lifting.DnfLifter;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13
14import java.util.HashMap;
15import java.util.Map;
16
17class PartialQueryRewriter extends AbstractRecursiveRewriter {
18 private final DnfLifter lifter;
19 private final Map<PartialRelation, PartialRelationRewriter> relationRewriterMap = new HashMap<>();
20
21 PartialQueryRewriter(DnfLifter lifter) {
22 this.lifter = lifter;
23 }
24
25 DnfLifter getLifter() {
26 return lifter;
27 }
28
29 PartialRelationRewriter getRelationRewriter(PartialRelation partialRelation) {
30 var rewriter = relationRewriterMap.get(partialRelation);
31 if (rewriter == null) {
32 throw new IllegalArgumentException("Do not know how to interpret partial relation: " + partialRelation);
33 }
34 return rewriter;
35 }
36
37 public void addRelationRewriter(PartialRelation partialRelation, PartialRelationRewriter interpreter) {
38 if (relationRewriterMap.put(partialRelation, interpreter) != null) {
39 throw new IllegalArgumentException("Duplicate partial relation: " + partialRelation);
40 }
41 }
42
43 @Override
44 protected Dnf doRewrite(Dnf dnf) {
45 var builder = Dnf.builderFrom(dnf);
46 for (var clause : dnf.getClauses()) {
47 var clauseRewriter = new PartialClauseRewriter(this);
48 var rewrittenClauses = clauseRewriter.rewriteClause(clause);
49 builder.clause(rewrittenClauses);
50 }
51 return builder.build();
52 }
53}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java
index 1bd3ad2e..f91fdd07 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java
@@ -5,20 +5,114 @@
5 */ 5 */
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.model.Interpretation;
8import tools.refinery.store.model.Model; 10import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter; 11import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.PartialInterpretation; 12import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation;
13import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner;
16import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
17import tools.refinery.store.reasoning.refinement.StorageRefiner;
18import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11import tools.refinery.store.reasoning.representation.PartialSymbol; 19import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.query.dnf.Dnf; 20import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
13import tools.refinery.store.query.resultset.ResultSet; 21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.cardinality.CardinalityInterval;
23import tools.refinery.store.representation.cardinality.CardinalityIntervals;
24import tools.refinery.store.tuple.Tuple;
25import tools.refinery.store.tuple.Tuple1;
14 26
15public class ReasoningAdapterImpl implements ReasoningAdapter { 27import java.util.HashMap;
28import java.util.Map;
29
30class ReasoningAdapterImpl implements ReasoningAdapter {
31 static final Symbol<Integer> NODE_COUNT_SYMBOL = Symbol.of("MODEL_SIZE", 0, Integer.class, 0);
16 private final Model model; 32 private final Model model;
17 private final ReasoningStoreAdapterImpl storeAdapter; 33 private final ReasoningStoreAdapterImpl storeAdapter;
34 private final Map<AnyPartialSymbol, AnyPartialInterpretation>[] partialInterpretations;
35 private final Map<AnyPartialSymbol, AnyPartialInterpretationRefiner> refiners;
36 private final StorageRefiner[] storageRefiners;
37 private final Interpretation<Integer> nodeCountInterpretation;
38 private final Interpretation<CardinalityInterval> countInterpretation;
18 39
19 ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) { 40 ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) {
20 this.model = model; 41 this.model = model;
21 this.storeAdapter = storeAdapter; 42 this.storeAdapter = storeAdapter;
43
44 int concretenessLength = Concreteness.values().length;
45 // Creation of a generic array.
46 @SuppressWarnings({"unchecked", "squid:S1905"})
47 var interpretationsArray = (Map<AnyPartialSymbol, AnyPartialInterpretation>[]) new Map[concretenessLength];
48 partialInterpretations = interpretationsArray;
49 createPartialInterpretations();
50
51 var refinerFactories = storeAdapter.getSymbolRefiners();
52 refiners = new HashMap<>(refinerFactories.size());
53 createRefiners();
54
55 storageRefiners = storeAdapter.createStorageRefiner(model);
56
57 nodeCountInterpretation = model.getInterpretation(NODE_COUNT_SYMBOL);
58 if (model.getStore().getSymbols().contains(MultiObjectTranslator.COUNT_STORAGE)) {
59 countInterpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE);
60 } else {
61 countInterpretation = null;
62 }
63 }
64
65 private void createPartialInterpretations() {
66 var supportedInterpretations = storeAdapter.getSupportedInterpretations();
67 int concretenessLength = Concreteness.values().length;
68 var interpretationFactories = storeAdapter.getSymbolInterpreters();
69 for (int i = 0; i < concretenessLength; i++) {
70 var concreteness = Concreteness.values()[i];
71 if (supportedInterpretations.contains(concreteness)) {
72 partialInterpretations[i] = new HashMap<>(interpretationFactories.size());
73 }
74 }
75 // Create the partial interpretations in order so that factories may refer to interpretations of symbols
76 // preceding them in the ordered {@code interpretationFactories} map, e.g., for opposite interpretations.
77 for (var entry : interpretationFactories.entrySet()) {
78 var partialSymbol = entry.getKey();
79 var factory = entry.getValue();
80 for (int i = 0; i < concretenessLength; i++) {
81 if (partialInterpretations[i] != null) {
82 var concreteness = Concreteness.values()[i];
83 var interpretation = createPartialInterpretation(concreteness, factory, partialSymbol);
84 partialInterpretations[i].put(partialSymbol, interpretation);
85 }
86 }
87 }
88 }
89
90 private <A, C> PartialInterpretation<A, C> createPartialInterpretation(
91 Concreteness concreteness, PartialInterpretation.Factory<A, C> interpreter, AnyPartialSymbol symbol) {
92 // The builder only allows well-typed assignment of interpreters to symbols.
93 @SuppressWarnings("unchecked")
94 var typedSymbol = (PartialSymbol<A, C>) symbol;
95 return interpreter.create(this, concreteness, typedSymbol);
96 }
97
98 private void createRefiners() {
99 var refinerFactories = storeAdapter.getSymbolRefiners();
100 // Create the partial interpretations refiners in order so that factories may refer to refiners of symbols
101 // preceding them in the ordered {@code interpretationFactories} map, e.g., for opposite interpretations.
102 for (var entry : refinerFactories.entrySet()) {
103 var partialSymbol = entry.getKey();
104 var factory = entry.getValue();
105 var refiner = createRefiner(factory, partialSymbol);
106 refiners.put(partialSymbol, refiner);
107 }
108 }
109
110 private <A, C> PartialInterpretationRefiner<A, C> createRefiner(
111 PartialInterpretationRefiner.Factory<A, C> factory, AnyPartialSymbol symbol) {
112 // The builder only allows well-typed assignment of interpreters to symbols.
113 @SuppressWarnings("unchecked")
114 var typedSymbol = (PartialSymbol<A, C>) symbol;
115 return factory.create(this, typedSymbol);
22 } 116 }
23 117
24 @Override 118 @Override
@@ -32,12 +126,82 @@ public class ReasoningAdapterImpl implements ReasoningAdapter {
32 } 126 }
33 127
34 @Override 128 @Override
35 public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { 129 public <A, C> PartialInterpretation<A, C> getPartialInterpretation(Concreteness concreteness,
36 return null; 130 PartialSymbol<A, C> partialSymbol) {
131 var map = partialInterpretations[concreteness.ordinal()];
132 if (map == null) {
133 throw new IllegalArgumentException("No interpretation for concreteness: " + concreteness);
134 }
135 var interpretation = map.get(partialSymbol);
136 if (interpretation == null) {
137 throw new IllegalArgumentException("No interpretation for partial symbol: " + partialSymbol);
138 }
139 // The builder only allows well-typed assignment of interpreters to symbols.
140 @SuppressWarnings("unchecked")
141 var typedInterpretation = (PartialInterpretation<A, C>) interpretation;
142 return typedInterpretation;
143 }
144
145 @Override
146 public <A, C> PartialInterpretationRefiner<A, C> getRefiner(PartialSymbol<A, C> partialSymbol) {
147 var refiner = refiners.get(partialSymbol);
148 if (refiner == null) {
149 throw new IllegalArgumentException("No refiner for partial symbol: " + partialSymbol);
150 }
151 // The builder only allows well-typed assignment of refiners to symbols.
152 @SuppressWarnings("unchecked")
153 var typedRefiner = (PartialInterpretationRefiner<A, C>) refiner;
154 return typedRefiner;
155 }
156
157 @Override
158 @Nullable
159 public Tuple1 split(int parentNode) {
160 int newNodeId = nodeCountInterpretation.get(Tuple.of());
161 nodeCountInterpretation.put(Tuple.of(), newNodeId + 1);
162 // Avoid creating an iterator object.
163 //noinspection ForLoopReplaceableByForEach
164 for (int i = 0; i < storageRefiners.length; i++) {
165 if (!storageRefiners[i].split(parentNode, newNodeId)) {
166 return null;
167 }
168 }
169 return Tuple.of(newNodeId);
170 }
171
172 @Override
173 public @Nullable Tuple1 focus(int parentObject) {
174 if (countInterpretation == null) {
175 throw new IllegalStateException("Cannot focus without " + MultiObjectTranslator.class.getSimpleName());
176 }
177 var tuple = Tuple.of(parentObject);
178 var count = countInterpretation.get(tuple);
179 if (CardinalityIntervals.ONE.equals(count)) {
180 return tuple;
181 }
182 if (CardinalityIntervals.LONE.equals(count)) {
183 countInterpretation.put(tuple, CardinalityIntervals.ONE);
184 return tuple;
185 }
186 if (CardinalityIntervals.NONE.equals(count)) {
187 return null;
188 }
189 return split(parentObject);
37 } 190 }
38 191
39 @Override 192 @Override
40 public ResultSet<Boolean> getLiftedResultSet(Dnf query) { 193 public boolean cleanup(int nodeToDelete) {
41 return null; 194 // Avoid creating an iterator object.
195 //noinspection ForLoopReplaceableByForEach
196 for (int i = 0; i < storageRefiners.length; i++) {
197 if (!storageRefiners[i].cleanup(nodeToDelete)) {
198 return false;
199 }
200 }
201 int currentModelSize = nodeCountInterpretation.get(Tuple.of());
202 if (nodeToDelete == currentModelSize - 1) {
203 nodeCountInterpretation.put(Tuple.of(), nodeToDelete);
204 }
205 return true;
42 } 206 }
43} 207}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java
index aa71496c..d2cd2eb0 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java
@@ -6,26 +6,166 @@
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.objectives.Objective;
11import tools.refinery.store.dse.transition.objectives.Objectives;
9import tools.refinery.store.model.ModelStore; 12import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.query.ModelQueryBuilder;
10import tools.refinery.store.query.dnf.Dnf; 15import tools.refinery.store.query.dnf.Dnf;
16import tools.refinery.store.query.dnf.FunctionalQuery;
17import tools.refinery.store.query.dnf.Query;
18import tools.refinery.store.query.dnf.RelationalQuery;
11import tools.refinery.store.reasoning.ReasoningBuilder; 19import tools.refinery.store.reasoning.ReasoningBuilder;
20import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
21import tools.refinery.store.reasoning.refinement.DefaultStorageRefiner;
22import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator;
23import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
24import tools.refinery.store.reasoning.lifting.DnfLifter;
25import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.literal.Modality; 26import tools.refinery.store.reasoning.literal.Modality;
27import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
28import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
29import tools.refinery.store.reasoning.refinement.StorageRefiner;
30import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
31import tools.refinery.store.representation.AnySymbol;
32import tools.refinery.store.representation.Symbol;
33
34import java.util.*;
13 35
14public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl> 36public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl>
15 implements ReasoningBuilder { 37 implements ReasoningBuilder {
38 private final DnfLifter lifter = new DnfLifter();
39 private final PartialQueryRewriter queryRewriter = new PartialQueryRewriter(lifter);
40 private Set<Concreteness> requiredInterpretations = Set.of(Concreteness.values());
41 private final Map<AnyPartialSymbol, AnyPartialSymbolTranslator> translators = new LinkedHashMap<>();
42 private final Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters =
43 new LinkedHashMap<>();
44 private final Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners =
45 new LinkedHashMap<>();
46 private final Map<AnySymbol, StorageRefiner.Factory<?>> registeredStorageRefiners = new LinkedHashMap<>();
47 private final List<PartialModelInitializer> initializers = new ArrayList<>();
48 private final List<Objective> objectives = new ArrayList<>();
49
50 @Override
51 public ReasoningBuilder requiredInterpretations(Collection<Concreteness> requiredInterpretations) {
52 this.requiredInterpretations = Set.copyOf(requiredInterpretations);
53 return this;
54 }
55
56 @Override
57 public ReasoningBuilder partialSymbol(AnyPartialSymbolTranslator translator) {
58 var partialSymbol = translator.getPartialSymbol();
59 var oldConfiguration = translators.put(partialSymbol, translator);
60 if (oldConfiguration != null && oldConfiguration != translator) {
61 throw new IllegalArgumentException("Duplicate configuration for symbol: " + partialSymbol);
62 }
63 return this;
64 }
65
16 @Override 66 @Override
17 public ReasoningBuilder liftedQuery(Dnf liftedQuery) { 67 public <T> ReasoningBuilder storageRefiner(Symbol<T> symbol, StorageRefiner.Factory<T> refiner) {
18 return null; 68 checkNotConfigured();
69 if (registeredStorageRefiners.put(symbol, refiner) != null) {
70 throw new IllegalArgumentException("Duplicate representation refiner for symbol: " + symbol);
71 }
72 return this;
73 }
74
75 @Override
76 public ReasoningBuilder initializer(PartialModelInitializer initializer) {
77 checkNotConfigured();
78 initializers.add(initializer);
79 return this;
19 } 80 }
20 81
21 @Override 82 @Override
22 public Dnf lift(Modality modality, Dnf query) { 83 public ReasoningBuilder objective(Objective objective) {
23 checkNotConfigured(); 84 checkNotConfigured();
24 return null; 85 objectives.add(objective);
86 return this;
87 }
88
89 @Override
90 public <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query) {
91 return lifter.lift(modality, concreteness, query);
92 }
93
94 @Override
95 public RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query) {
96 return lifter.lift(modality, concreteness, query);
97 }
98
99 @Override
100 public <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query) {
101 return lifter.lift(modality, concreteness, query);
102 }
103
104 @Override
105 public Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf) {
106 return lifter.lift(modality, concreteness, dnf);
107 }
108
109 @Override
110 protected void doConfigure(ModelStoreBuilder storeBuilder) {
111 storeBuilder.symbols(ReasoningAdapterImpl.NODE_COUNT_SYMBOL);
112 for (var translator : translators.values()) {
113 translator.configure(storeBuilder);
114 if (translator instanceof PartialRelationTranslator relationConfiguration) {
115 doConfigure(storeBuilder, relationConfiguration);
116 } else {
117 throw new IllegalArgumentException("Unknown partial symbol translator %s for partial symbol %s"
118 .formatted(translator, translator.getPartialSymbol()));
119 }
120 }
121 storeBuilder.symbols(registeredStorageRefiners.keySet());
122 var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class);
123 queryBuilder.rewriter(queryRewriter);
124 if (!objectives.isEmpty()) {
125 storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class)
126 .ifPresent(dseBuilder -> dseBuilder.objective(Objectives.sum(objectives)));
127 }
128 }
129
130 private void doConfigure(ModelStoreBuilder storeBuilder, PartialRelationTranslator relationConfiguration) {
131 var partialRelation = relationConfiguration.getPartialRelation();
132 queryRewriter.addRelationRewriter(partialRelation, relationConfiguration.getRewriter());
133 var interpretationFactory = relationConfiguration.getInterpretationFactory();
134 interpretationFactory.configure(storeBuilder, requiredInterpretations);
135 symbolInterpreters.put(partialRelation, interpretationFactory);
136 var refiner = relationConfiguration.getInterpretationRefiner();
137 if (refiner != null) {
138 symbolRefiners.put(partialRelation, refiner);
139 }
25 } 140 }
26 141
27 @Override 142 @Override
28 public ReasoningStoreAdapterImpl doBuild(ModelStore store) { 143 public ReasoningStoreAdapterImpl doBuild(ModelStore store) {
29 return null; 144 return new ReasoningStoreAdapterImpl(store, requiredInterpretations,
145 Collections.unmodifiableMap(symbolInterpreters), Collections.unmodifiableMap(symbolRefiners),
146 getStorageRefiners(store), Collections.unmodifiableList(initializers));
147 }
148
149 private Map<AnySymbol, StorageRefiner.Factory<?>> getStorageRefiners(ModelStore store) {
150 var symbols = store.getSymbols();
151 var storageRefiners = new LinkedHashMap<AnySymbol, StorageRefiner.Factory<?>>(symbols.size());
152 for (var symbol : symbols) {
153 var refiner = registeredStorageRefiners.remove(symbol);
154 if (refiner == null) {
155 if (symbol.arity() == 0) {
156 // Arity-0 symbols don't need a default refiner, because they are unaffected by object
157 // creation/removal. Only a custom refiner ({@code refiner != null}) might need to update them.
158 continue;
159 }
160 // By default, copy over all affected tuples on object creation and remove all affected tuples on
161 // object removal.
162 refiner = DefaultStorageRefiner.factory();
163 }
164 storageRefiners.put(symbol, refiner);
165 }
166 if (!registeredStorageRefiners.isEmpty()) {
167 throw new IllegalArgumentException("Unused storage refiners: " + registeredStorageRefiners.keySet());
168 }
169 return Collections.unmodifiableMap(storageRefiners);
30 } 170 }
31} 171}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java
index cdddd8d6..8eb5a034 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java
@@ -5,19 +5,45 @@
5 */ 5 */
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import tools.refinery.store.reasoning.ReasoningStoreAdapter;
9import tools.refinery.store.model.Model; 8import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore; 9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.reasoning.ReasoningStoreAdapter;
12import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Concreteness;
14import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
15import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
16import tools.refinery.store.reasoning.refinement.StorageRefiner;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 17import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.query.dnf.Dnf; 18import tools.refinery.store.reasoning.seed.ModelSeed;
19import tools.refinery.store.representation.AnySymbol;
20import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.tuple.Tuple;
13 22
14import java.util.Collection; 23import java.util.Collection;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
15 27
16public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { 28class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter {
17 private final ModelStore store; 29 private final ModelStore store;
30 private final Set<Concreteness> supportedInterpretations;
31 private final Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters;
32 private final Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners;
33 private final Map<AnySymbol, StorageRefiner.Factory<?>> storageRefiners;
34 private final List<PartialModelInitializer> initializers;
18 35
19 ReasoningStoreAdapterImpl(ModelStore store) { 36 ReasoningStoreAdapterImpl(ModelStore store, Set<Concreteness> supportedInterpretations,
37 Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters,
38 Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners,
39 Map<AnySymbol, StorageRefiner.Factory<?>> storageRefiners,
40 List<PartialModelInitializer> initializers) {
20 this.store = store; 41 this.store = store;
42 this.supportedInterpretations = supportedInterpretations;
43 this.symbolInterpreters = symbolInterpreters;
44 this.symbolRefiners = symbolRefiners;
45 this.storageRefiners = storageRefiners;
46 this.initializers = initializers;
21 } 47 }
22 48
23 @Override 49 @Override
@@ -26,13 +52,59 @@ public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter {
26 } 52 }
27 53
28 @Override 54 @Override
55 public Set<Concreteness> getSupportedInterpretations() {
56 return supportedInterpretations;
57 }
58
59 @Override
29 public Collection<AnyPartialSymbol> getPartialSymbols() { 60 public Collection<AnyPartialSymbol> getPartialSymbols() {
30 return null; 61 return symbolInterpreters.keySet();
31 } 62 }
32 63
33 @Override 64 @Override
34 public Collection<Dnf> getLiftedQueries() { 65 public Collection<AnyPartialSymbol> getRefinablePartialSymbols() {
35 return null; 66 return symbolRefiners.keySet();
67 }
68
69 // Use of wildcard return value only in internal method not exposed as API, so there is less chance of confusion.
70 @SuppressWarnings("squid:S1452")
71 Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> getSymbolInterpreters() {
72 return symbolInterpreters;
73 }
74
75 // Use of wildcard return value only in internal method not exposed as API, so there is less chance of confusion.
76 @SuppressWarnings("squid:S1452")
77 Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> getSymbolRefiners() {
78 return symbolRefiners;
79 }
80
81 StorageRefiner[] createStorageRefiner(Model model) {
82 var refiners = new StorageRefiner[storageRefiners.size()];
83 int i = 0;
84 for (var entry : storageRefiners.entrySet()) {
85 var symbol = entry.getKey();
86 var factory = entry.getValue();
87 refiners[i] = createStorageRefiner(factory, model, symbol);
88 i++;
89 }
90 return refiners;
91 }
92
93 private <T> StorageRefiner createStorageRefiner(StorageRefiner.Factory<T> factory, Model model, AnySymbol symbol) {
94 // The builder only allows well-typed assignment of refiners to symbols.
95 @SuppressWarnings("unchecked")
96 var typedSymbol = (Symbol<T>) symbol;
97 return factory.create(typedSymbol, model);
98 }
99
100 public Model createInitialModel(ModelSeed modelSeed) {
101 var model = store.createEmptyModel();
102 model.getInterpretation(ReasoningAdapterImpl.NODE_COUNT_SYMBOL).put(Tuple.of(), modelSeed.getNodeCount());
103 for (var initializer : initializers) {
104 initializer.initialize(model, modelSeed);
105 }
106 model.getAdapter(ModelQueryAdapter.class).flushChanges();
107 return model;
36 } 108 }
37 109
38 @Override 110 @Override
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java
new file mode 100644
index 00000000..ed291eac
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.literal.Concreteness;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11
12public abstract class AbstractPartialInterpretation<A, C> implements PartialInterpretation<A, C> {
13 private final ReasoningAdapter adapter;
14 private final PartialSymbol<A, C> partialSymbol;
15 private final Concreteness concreteness;
16
17 protected AbstractPartialInterpretation(ReasoningAdapter adapter, Concreteness concreteness,
18 PartialSymbol<A, C> partialSymbol) {
19 this.adapter = adapter;
20 this.partialSymbol = partialSymbol;
21 this.concreteness = concreteness;
22 }
23
24 @Override
25 public ReasoningAdapter getAdapter() {
26 return adapter;
27 }
28
29 @Override
30 public PartialSymbol<A, C> getPartialSymbol() {
31 return partialSymbol;
32 }
33
34 @Override
35 public Concreteness getConcreteness() {
36 return concreteness;
37 }
38}
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/interpretation/AnyPartialInterpretation.java
index 000171a1..cd709bc4 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AnyPartialInterpretation.java
@@ -3,8 +3,10 @@
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning.interpretation;
7 7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.literal.Concreteness;
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 10import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9 11
10public sealed interface AnyPartialInterpretation permits PartialInterpretation { 12public sealed interface AnyPartialInterpretation permits PartialInterpretation {
@@ -12,7 +14,5 @@ public sealed interface AnyPartialInterpretation permits PartialInterpretation {
12 14
13 AnyPartialSymbol getPartialSymbol(); 15 AnyPartialSymbol getPartialSymbol();
14 16
15 int countUnfinished(); 17 Concreteness getConcreteness();
16
17 int countErrors();
18} 18}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java
new file mode 100644
index 00000000..86ffe751
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java
@@ -0,0 +1,34 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.tuple.Tuple;
14
15import java.util.Set;
16
17public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation {
18 @Override
19 PartialSymbol<A, C> getPartialSymbol();
20
21 A get(Tuple key);
22
23 Cursor<Tuple, A> getAll();
24
25 @FunctionalInterface
26 interface Factory<A, C> {
27 PartialInterpretation<A, C> create(ReasoningAdapter adapter, Concreteness concreteness,
28 PartialSymbol<A, C> partialSymbol);
29
30 default void configure(ModelStoreBuilder storeBuilder, Set<Concreteness> requiredInterpretations) {
31 // Nothing to configure by default.
32 }
33 }
34}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java
new file mode 100644
index 00000000..6ad35c20
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.query.literal.AbstractCallLiteral;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.literal.Modality;
13
14import java.util.List;
15import java.util.Set;
16
17@FunctionalInterface
18public interface PartialRelationRewriter {
19 List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, Modality modality,
20 Concreteness concreteness);
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java
new file mode 100644
index 00000000..5cdaa185
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java
@@ -0,0 +1,195 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.resultset.ResultSet;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.representation.PartialSymbol;
17import tools.refinery.store.representation.TruthValue;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.Set;
21
22public class QueryBasedRelationInterpretationFactory implements PartialInterpretation.Factory<TruthValue, Boolean> {
23 private final Query<Boolean> may;
24 private final Query<Boolean> must;
25 private final Query<Boolean> candidateMay;
26 private final Query<Boolean> candidateMust;
27
28 public QueryBasedRelationInterpretationFactory(
29 Query<Boolean> may, Query<Boolean> must, Query<Boolean> candidateMay, Query<Boolean> candidateMust) {
30 this.may = may;
31 this.must = must;
32 this.candidateMay = candidateMay;
33 this.candidateMust = candidateMust;
34 }
35
36 @Override
37 public PartialInterpretation<TruthValue, Boolean> create(
38 ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol) {
39 var queryEngine = adapter.getModel().getAdapter(ModelQueryAdapter.class);
40 ResultSet<Boolean> mayResultSet;
41 ResultSet<Boolean> mustResultSet;
42 switch (concreteness) {
43 case PARTIAL -> {
44 mayResultSet = queryEngine.getResultSet(may);
45 mustResultSet = queryEngine.getResultSet(must);
46 }
47 case CANDIDATE -> {
48 mayResultSet = queryEngine.getResultSet(candidateMay);
49 mustResultSet = queryEngine.getResultSet(candidateMust);
50 }
51 default -> throw new IllegalArgumentException("Unknown concreteness: " + concreteness);
52 }
53 if (mayResultSet.equals(mustResultSet)) {
54 return new TwoValuedInterpretation(adapter, concreteness, partialSymbol, mustResultSet);
55 } else {
56 return new FourValuedInterpretation(
57 adapter, concreteness, partialSymbol, mayResultSet, mustResultSet);
58 }
59 }
60
61 @Override
62 public void configure(ModelStoreBuilder storeBuilder, Set<Concreteness> requiredInterpretations) {
63 var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class);
64 if (requiredInterpretations.contains(Concreteness.PARTIAL)) {
65 queryBuilder.queries(may, must);
66 }
67 if (requiredInterpretations.contains(Concreteness.CANDIDATE)) {
68 queryBuilder.queries(candidateMay, candidateMust);
69 }
70 }
71
72 private static class TwoValuedInterpretation extends AbstractPartialInterpretation<TruthValue, Boolean> {
73 private final ResultSet<Boolean> resultSet;
74
75 protected TwoValuedInterpretation(
76 ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol,
77 ResultSet<Boolean> resultSet) {
78 super(adapter, concreteness, partialSymbol);
79 this.resultSet = resultSet;
80 }
81
82 @Override
83 public TruthValue get(Tuple key) {
84 return TruthValue.toTruthValue(resultSet.get(key));
85 }
86
87 @Override
88 public Cursor<Tuple, TruthValue> getAll() {
89 return new TwoValuedCursor(resultSet.getAll());
90 }
91
92 private record TwoValuedCursor(Cursor<Tuple, Boolean> cursor) implements Cursor<Tuple, TruthValue> {
93 @Override
94 public Tuple getKey() {
95 return cursor.getKey();
96 }
97
98 @Override
99 public TruthValue getValue() {
100 return TruthValue.toTruthValue(cursor.getValue());
101 }
102
103 @Override
104 public boolean isTerminated() {
105 return cursor.isTerminated();
106 }
107
108 @Override
109 public boolean move() {
110 return cursor.move();
111 }
112 }
113 }
114
115 private static class FourValuedInterpretation extends AbstractPartialInterpretation<TruthValue, Boolean> {
116 private final ResultSet<Boolean> mayResultSet;
117 private final ResultSet<Boolean> mustResultSet;
118
119 public FourValuedInterpretation(
120 ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol,
121 ResultSet<Boolean> mayResultSet, ResultSet<Boolean> mustResultSet) {
122 super(adapter, concreteness, partialSymbol);
123 this.mayResultSet = mayResultSet;
124 this.mustResultSet = mustResultSet;
125 }
126
127 @Override
128 public TruthValue get(Tuple key) {
129 boolean isMay = mayResultSet.get(key);
130 boolean isMust = mustResultSet.get(key);
131 if (isMust) {
132 return isMay ? TruthValue.TRUE : TruthValue.ERROR;
133 } else {
134 return isMay ? TruthValue.UNKNOWN : TruthValue.FALSE;
135 }
136 }
137
138 @Override
139 public Cursor<Tuple, TruthValue> getAll() {
140 return new FourValuedCursor();
141 }
142
143 private final class FourValuedCursor implements Cursor<Tuple, TruthValue> {
144 private final Cursor<Tuple, Boolean> mayCursor;
145 private Cursor<Tuple, Boolean> mustCursor;
146
147 private FourValuedCursor() {
148 this.mayCursor = mayResultSet.getAll();
149 }
150
151 @Override
152 public Tuple getKey() {
153 return mustCursor == null ? mayCursor.getKey() : mustCursor.getKey();
154 }
155
156 @Override
157 public TruthValue getValue() {
158 if (mustCursor != null) {
159 return TruthValue.ERROR;
160 }
161 if (Boolean.TRUE.equals(mustResultSet.get(mayCursor.getKey()))) {
162 return TruthValue.TRUE;
163 }
164 return TruthValue.UNKNOWN;
165 }
166
167 @Override
168 public boolean isTerminated() {
169 return mustCursor != null && mustCursor.isTerminated();
170 }
171
172 @Override
173 public boolean move() {
174 if (mayCursor.isTerminated()) {
175 return moveMust();
176 }
177 if (mayCursor.move()) {
178 return true;
179 }
180 mustCursor = mustResultSet.getAll();
181 return moveMust();
182 }
183
184 private boolean moveMust() {
185 while (mustCursor.move()) {
186 // We already iterated over {@code TRUE} truth values with {@code mayCursor}.
187 if (!Boolean.TRUE.equals(mayResultSet.get(mustCursor.getKey()))) {
188 return true;
189 }
190 }
191 return false;
192 }
193 }
194 }
195}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java
new file mode 100644
index 00000000..78fdbb89
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java
@@ -0,0 +1,63 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.query.dnf.RelationalQuery;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.reasoning.literal.Concreteness;
13import tools.refinery.store.reasoning.literal.Modality;
14
15import java.util.List;
16import java.util.Set;
17
18public class QueryBasedRelationRewriter implements PartialRelationRewriter {
19 private final RelationalQuery may;
20 private final RelationalQuery must;
21 private final RelationalQuery candidateMay;
22 private final RelationalQuery candidateMust;
23
24 public QueryBasedRelationRewriter(RelationalQuery may, RelationalQuery must, RelationalQuery candidateMay,
25 RelationalQuery candidateMust) {
26 this.may = may;
27 this.must = must;
28 this.candidateMay = candidateMay;
29 this.candidateMust = candidateMust;
30 }
31
32 public RelationalQuery getMay() {
33 return may;
34 }
35
36 public RelationalQuery getMust() {
37 return must;
38 }
39
40 public RelationalQuery getCandidateMay() {
41 return candidateMay;
42 }
43
44 public RelationalQuery getCandidateMust() {
45 return candidateMust;
46 }
47
48 @Override
49 public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal,
50 Modality modality, Concreteness concreteness) {
51 var query = switch (concreteness) {
52 case PARTIAL -> switch (modality) {
53 case MAY -> may;
54 case MUST -> must;
55 };
56 case CANDIDATE -> switch (modality) {
57 case MAY -> candidateMay;
58 case MUST -> candidateMust;
59 };
60 };
61 return List.of(literal.withTarget(query.getDnf()));
62 }
63}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java
new file mode 100644
index 00000000..17916c02
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java
@@ -0,0 +1,182 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.lifting;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.DnfClause;
11import tools.refinery.store.query.literal.*;
12import tools.refinery.store.query.term.NodeVariable;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.reasoning.ReasoningAdapter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.literal.ModalConstraint;
18import tools.refinery.store.reasoning.literal.Modality;
19
20import java.util.*;
21import java.util.stream.Collectors;
22
23class ClauseLifter {
24 private final Modality modality;
25 private final Concreteness concreteness;
26 private final DnfClause clause;
27 private final Set<NodeVariable> quantifiedVariables;
28 private final Set<NodeVariable> existentialQuantifiersToAdd;
29
30 public ClauseLifter(Modality modality, Concreteness concreteness, Dnf dnf, DnfClause clause) {
31 this.modality = modality;
32 this.concreteness = concreteness;
33 this.clause = clause;
34 quantifiedVariables = getQuantifiedNodeVariables(dnf, clause);
35 existentialQuantifiersToAdd = new LinkedHashSet<>(quantifiedVariables);
36 }
37
38 private static Set<NodeVariable> getQuantifiedNodeVariables(Dnf dnf, DnfClause clause) {
39 var quantifiedVariables = clause.positiveVariables().stream()
40 .filter(Variable::isNodeVariable)
41 .map(Variable::asNodeVariable)
42 .collect(Collectors.toCollection(LinkedHashSet::new));
43 for (var symbolicParameter : dnf.getSymbolicParameters()) {
44 if (symbolicParameter.getVariable() instanceof NodeVariable nodeParameter) {
45 quantifiedVariables.remove(nodeParameter);
46 }
47 }
48 return Collections.unmodifiableSet(quantifiedVariables);
49 }
50
51 public List<Literal> liftClause() {
52 var liftedLiterals = new ArrayList<Literal>();
53 for (var literal : clause.literals()) {
54 var liftedLiteral = liftLiteral(literal);
55 liftedLiterals.add(liftedLiteral);
56 }
57 var existsConstraint = ModalConstraint.of(modality, concreteness, ReasoningAdapter.EXISTS_SYMBOL);
58 for (var quantifiedVariable : existentialQuantifiersToAdd) {
59 liftedLiterals.add(existsConstraint.call(quantifiedVariable));
60 }
61 return liftedLiterals;
62 }
63
64 private Literal liftLiteral(Literal literal) {
65 if (literal instanceof CallLiteral callLiteral) {
66 return liftCallLiteral(callLiteral);
67 } else if (literal instanceof EquivalenceLiteral equivalenceLiteral) {
68 return liftEquivalenceLiteral(equivalenceLiteral);
69 } else if (literal instanceof ConstantLiteral ||
70 literal instanceof AssignLiteral<?> ||
71 literal instanceof CheckLiteral) {
72 return literal;
73 } else if (literal instanceof AbstractCountLiteral<?>) {
74 throw new IllegalArgumentException("Count literal %s cannot be lifted".formatted(literal));
75 } else if (literal instanceof AggregationLiteral<?, ?>) {
76 throw new IllegalArgumentException("Aggregation literal %s cannot be lifted".formatted(literal));
77 } else if (literal instanceof RepresentativeElectionLiteral) {
78 throw new IllegalArgumentException("SCC literal %s cannot be lifted".formatted(literal));
79 } else {
80 throw new IllegalArgumentException("Unknown literal to lift: " + literal);
81 }
82 }
83
84 private Literal liftCallLiteral(CallLiteral callLiteral) {
85 var polarity = callLiteral.getPolarity();
86 return switch (polarity) {
87 case POSITIVE -> {
88 Constraint target = callLiteral.getTarget();
89 var arguments = callLiteral.getArguments();
90 yield ModalConstraint.of(modality, concreteness, target).call(CallPolarity.POSITIVE, arguments);
91 }
92 case NEGATIVE -> callNegationHelper(callLiteral);
93 case TRANSITIVE -> callTransitiveHelper(callLiteral);
94 };
95 }
96
97 private Literal callNegationHelper(CallLiteral callLiteral) {
98 var target = callLiteral.getTarget();
99 var originalArguments = callLiteral.getArguments();
100 var negatedModality = modality.negate();
101 var privateVariables = callLiteral.getPrivateVariables(clause.positiveVariables());
102 if (privateVariables.isEmpty()) {
103 // If there is no universal quantification, we may directly call the original Dnf.
104 return ModalConstraint.of(negatedModality, concreteness, target)
105 .call(CallPolarity.NEGATIVE, originalArguments);
106 }
107
108 var builder = Dnf.builder("%s#negation#%s#%s#%s"
109 .formatted(target.name(), modality, concreteness, privateVariables));
110 var uniqueOriginalArguments = List.copyOf(new LinkedHashSet<>(originalArguments));
111
112 var alwaysInputVariables = callLiteral.getInputVariables(Set.of());
113 for (var variable : uniqueOriginalArguments) {
114 var direction = alwaysInputVariables.contains(variable) ? ParameterDirection.IN : ParameterDirection.OUT;
115 builder.parameter(variable, direction);
116 }
117
118 var literals = new ArrayList<Literal>();
119 var liftedConstraint = ModalConstraint.of(negatedModality, concreteness, target);
120 literals.add(liftedConstraint.call(CallPolarity.POSITIVE, originalArguments));
121
122 var existsConstraint = ModalConstraint.of(negatedModality, concreteness, ReasoningAdapter.EXISTS_SYMBOL);
123 for (var variable : uniqueOriginalArguments) {
124 if (privateVariables.contains(variable)) {
125 literals.add(existsConstraint.call(variable));
126 }
127 }
128
129 builder.clause(literals);
130 var liftedTarget = builder.build();
131 return liftedTarget.call(CallPolarity.NEGATIVE, uniqueOriginalArguments);
132 }
133
134 private Literal callTransitiveHelper(CallLiteral callLiteral) {
135 var target = callLiteral.getTarget();
136 var originalArguments = callLiteral.getArguments();
137 var liftedTarget = ModalConstraint.of(modality, concreteness, target);
138
139 var existsConstraint = ModalConstraint.of(modality, concreteness, ReasoningAdapter.EXISTS_SYMBOL);
140 var existingEndHelperName = "%s#exisitingEnd#%s#%s".formatted(target.name(), modality, concreteness);
141 var existingEndHelper = Dnf.of(existingEndHelperName, builder -> {
142 var start = builder.parameter("start");
143 var end = builder.parameter("end");
144 builder.clause(
145 liftedTarget.call(start, end),
146 existsConstraint.call(end)
147 );
148 });
149
150 // The start and end of a transitive path is always a node.
151 var pathEnd = (NodeVariable) originalArguments.get(1);
152 if (quantifiedVariables.contains(pathEnd)) {
153 // The end of the path needs existential quantification anyway, so we don't need a second helper.
154 // We replace the call to EXISTS with the transitive path call.
155 existentialQuantifiersToAdd.remove(pathEnd);
156 return existingEndHelper.call(CallPolarity.TRANSITIVE, originalArguments);
157 }
158
159 var transitiveHelperName = "%s#transitive#%s#%s".formatted(target.name(), modality, concreteness);
160 var transitiveHelper = Dnf.of(transitiveHelperName, builder -> {
161 var start = builder.parameter("start");
162 var end = builder.parameter("end");
163 // Make sure the end of the path is not existentially quantified.
164 builder.clause(liftedTarget.call(start, end));
165 builder.clause(middle -> List.of(
166 existingEndHelper.callTransitive(start, middle),
167 liftedTarget.call(middle, end)
168 ));
169 });
170
171 return transitiveHelper.call(CallPolarity.POSITIVE, originalArguments);
172 }
173
174 private Literal liftEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral) {
175 if (equivalenceLiteral.isPositive()) {
176 return ModalConstraint.of(modality, concreteness, ReasoningAdapter.EQUALS_SYMBOL)
177 .call(CallPolarity.POSITIVE, equivalenceLiteral.getLeft(), equivalenceLiteral.getRight());
178 }
179 return ModalConstraint.of(modality.negate(), concreteness, ReasoningAdapter.EQUALS_SYMBOL)
180 .call(CallPolarity.NEGATIVE, equivalenceLiteral.getLeft(), equivalenceLiteral.getRight());
181 }
182}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java
index ac41d170..889f595f 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java
@@ -5,124 +5,68 @@
5 */ 5 */
6package tools.refinery.store.reasoning.lifting; 6package tools.refinery.store.reasoning.lifting;
7 7
8import org.jetbrains.annotations.Nullable; 8import tools.refinery.store.query.dnf.*;
9import tools.refinery.store.query.dnf.Dnf; 9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.dnf.DnfBuilder;
11import tools.refinery.store.query.dnf.DnfClause;
12import tools.refinery.store.query.literal.CallLiteral;
13import tools.refinery.store.query.literal.CallPolarity;
14import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
15import tools.refinery.store.query.term.NodeVariable; 11import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.reasoning.ReasoningAdapter;
18import tools.refinery.store.reasoning.literal.ModalConstraint;
19import tools.refinery.store.reasoning.literal.Modality; 12import tools.refinery.store.reasoning.literal.Modality;
20import tools.refinery.store.reasoning.literal.PartialLiterals;
21import tools.refinery.store.util.CycleDetectingMapper;
22 13
23import java.util.ArrayList; 14import java.util.HashMap;
24import java.util.LinkedHashSet;
25import java.util.List; 15import java.util.List;
16import java.util.Map;
26 17
27public class DnfLifter { 18public class DnfLifter {
28 private final CycleDetectingMapper<ModalDnf, Dnf> mapper = new CycleDetectingMapper<>(ModalDnf::toString, 19 private final Map<ModalDnf, Dnf> cache = new HashMap<>();
29 this::doLift);
30 20
31 public Dnf lift(Modality modality, Dnf query) { 21 public <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query) {
32 return mapper.map(new ModalDnf(modality, query)); 22 var liftedDnf = lift(modality, concreteness, query.getDnf());
23 return query.withDnf(liftedDnf);
24 }
25
26 public RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query) {
27 var liftedDnf = lift(modality, concreteness, query.getDnf());
28 return query.withDnf(liftedDnf);
29 }
30
31 public <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query) {
32 var liftedDnf = lift(modality, concreteness, query.getDnf());
33 return query.withDnf(liftedDnf);
34 }
35
36 public Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf) {
37 return cache.computeIfAbsent(new ModalDnf(modality, concreteness, dnf), this::doLift);
33 } 38 }
34 39
35 private Dnf doLift(ModalDnf modalDnf) { 40 private Dnf doLift(ModalDnf modalDnf) {
36 var modality = modalDnf.modality(); 41 var modality = modalDnf.modality();
42 var concreteness = modalDnf.concreteness();
37 var dnf = modalDnf.dnf(); 43 var dnf = modalDnf.dnf();
38 var builder = Dnf.builder(); 44 var builder = Dnf.builder(decorateName(dnf.name(), modality, concreteness));
39 builder.symbolicParameters(dnf.getSymbolicParameters()); 45 builder.symbolicParameters(dnf.getSymbolicParameters());
40 boolean changed = false; 46 builder.functionalDependencies(dnf.getFunctionalDependencies());
41 for (var clause : dnf.getClauses()) { 47 for (var clause : dnf.getClauses()) {
42 if (liftClause(modality, dnf, clause, builder)) { 48 builder.clause(liftClause(modality, concreteness, dnf, clause));
43 changed = true;
44 }
45 } 49 }
46 if (changed) { 50 var liftedDnf = builder.build();
47 return builder.build(); 51 if (dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, liftedDnf)) {
52 return dnf;
48 } 53 }
49 return dnf; 54 return liftedDnf;
50 } 55 }
51 56
52 private boolean liftClause(Modality modality, Dnf originalDnf, DnfClause clause, DnfBuilder builder) { 57 private List<Literal> liftClause(Modality modality, Concreteness concreteness, Dnf dnf, DnfClause clause) {
53 boolean changed = false; 58 var lifter = new ClauseLifter(modality, concreteness, dnf, clause);
54 var quantifiedVariables = getQuantifiedDataVariables(originalDnf, clause); 59 return lifter.liftClause();
55 var literals = clause.literals();
56 var liftedLiterals = new ArrayList<Literal>(literals.size());
57 for (var literal : literals) {
58 Literal liftedLiteral = liftLiteral(modality, literal);
59 if (liftedLiteral == null) {
60 liftedLiteral = literal;
61 } else {
62 changed = true;
63 }
64 liftedLiterals.add(liftedLiteral);
65 var variable = isExistsLiteralForVariable(modality, liftedLiteral);
66 if (variable != null) {
67 // If we already quantify over the existence of the variable with the expected modality,
68 // we don't need to insert quantification manually.
69 quantifiedVariables.remove(variable);
70 }
71 }
72 for (var quantifiedVariable : quantifiedVariables) {
73 // Quantify over data variables that are not already quantified with the expected modality.
74 liftedLiterals.add(new CallLiteral(CallPolarity.POSITIVE,
75 new ModalConstraint(modality, ReasoningAdapter.EXISTS), List.of(quantifiedVariable)));
76 }
77 builder.clause(liftedLiterals);
78 return changed || !quantifiedVariables.isEmpty();
79 }
80
81 private static LinkedHashSet<Variable> getQuantifiedDataVariables(Dnf originalDnf, DnfClause clause) {
82 var quantifiedVariables = new LinkedHashSet<>(clause.positiveVariables());
83 for (var symbolicParameter : originalDnf.getSymbolicParameters()) {
84 // The existence of parameters will be checked outside this DNF.
85 quantifiedVariables.remove(symbolicParameter.getVariable());
86 }
87 quantifiedVariables.removeIf(variable -> !(variable instanceof NodeVariable));
88 return quantifiedVariables;
89 } 60 }
90 61
91 @Nullable 62 private record ModalDnf(Modality modality, Concreteness concreteness, Dnf dnf) {
92 private Variable isExistsLiteralForVariable(Modality modality, Literal literal) { 63 @Override
93 if (literal instanceof CallLiteral callLiteral && 64 public String toString() {
94 callLiteral.getPolarity() == CallPolarity.POSITIVE && 65 return "%s %s %s".formatted(modality, concreteness, dnf.name());
95 callLiteral.getTarget() instanceof ModalConstraint modalConstraint &&
96 modalConstraint.modality() == modality &&
97 modalConstraint.constraint().equals(ReasoningAdapter.EXISTS)) {
98 return callLiteral.getArguments().get(0);
99 } 66 }
100 return null;
101 } 67 }
102 68
103 @Nullable 69 public static String decorateName(String name, Modality modality, Concreteness concreteness) {
104 private Literal liftLiteral(Modality modality, Literal literal) { 70 return "%s#%s#%s".formatted(name, modality, concreteness);
105 if (!(literal instanceof CallLiteral callLiteral)) {
106 return null;
107 }
108 var target = callLiteral.getTarget();
109 if (target instanceof ModalConstraint modalTarget) {
110 var actualTarget = modalTarget.constraint();
111 if (actualTarget instanceof Dnf dnf) {
112 var targetModality = modalTarget.modality();
113 var liftedTarget = lift(targetModality, dnf);
114 return new CallLiteral(callLiteral.getPolarity(), liftedTarget, callLiteral.getArguments());
115 }
116 // No more lifting to be done, pass any modal call to a partial symbol through.
117 return null;
118 } else if (target instanceof Dnf dnf) {
119 var polarity = callLiteral.getPolarity();
120 var liftedTarget = lift(modality.commute(polarity), dnf);
121 // Use == instead of equals(), because lift will return the same object by reference is there are no
122 // changes made during lifting.
123 return liftedTarget == target ? null : new CallLiteral(polarity, liftedTarget, callLiteral.getArguments());
124 } else {
125 return PartialLiterals.addModality(callLiteral, modality);
126 }
127 } 71 }
128} 72}
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
deleted file mode 100644
index 16fb8fbf..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java
+++ /dev/null
@@ -1,16 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.lifting;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.reasoning.literal.Modality;
10
11record ModalDnf(Modality modality, Dnf dnf) {
12 @Override
13 public String toString() {
14 return "%s %s".formatted(modality, dnf.name());
15 }
16}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java
new file mode 100644
index 00000000..43ac5904
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java
@@ -0,0 +1,18 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.literal;
7
8import java.util.Locale;
9
10public enum Concreteness {
11 PARTIAL,
12 CANDIDATE;
13
14 @Override
15 public String toString() {
16 return name().toLowerCase(Locale.ROOT);
17 }
18}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java
new file mode 100644
index 00000000..91dd2b72
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public class CountCandidateLowerBoundLiteral extends AbstractCountLiteral<Integer> {
19 public CountCandidateLowerBoundLiteral(DataVariable<Integer> resultVariable, Constraint target,
20 List<Variable> arguments) {
21 super(Integer.class, resultVariable, target, arguments);
22 }
23
24 @Override
25 protected Integer zero() {
26 return 0;
27 }
28
29 @Override
30 protected Integer one() {
31 return 1;
32 }
33
34 @Override
35 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
36 return new CountCandidateLowerBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
37 substitutedArguments);
38 }
39
40 @Override
41 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
42 return new CountCandidateLowerBoundLiteral(getResultVariable(), newTarget, newArguments);
43 }
44
45 @Override
46 protected String operatorName() {
47 return "@LowerBound(\"candidate\") count";
48 }
49}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java
new file mode 100644
index 00000000..94c9399d
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public class CountCandidateUpperBoundLiteral extends AbstractCountLiteral<Integer> {
19 public CountCandidateUpperBoundLiteral(DataVariable<Integer> resultVariable, Constraint target,
20 List<Variable> arguments) {
21 super(Integer.class, resultVariable, target, arguments);
22 }
23
24 @Override
25 protected Integer zero() {
26 return 0;
27 }
28
29 @Override
30 protected Integer one() {
31 return 1;
32 }
33
34 @Override
35 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
36 return new CountCandidateUpperBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
37 substitutedArguments);
38 }
39
40 @Override
41 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
42 return new CountCandidateUpperBoundLiteral(getResultVariable(), newTarget, newArguments);
43 }
44
45 @Override
46 protected String operatorName() {
47 return "@UpperBound(\"candidate\") count";
48 }
49}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java
new file mode 100644
index 00000000..b75b0cab
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java
@@ -0,0 +1,49 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public class CountLowerBoundLiteral extends AbstractCountLiteral<Integer> {
19 public CountLowerBoundLiteral(DataVariable<Integer> resultVariable, Constraint target,
20 List<Variable> arguments) {
21 super(Integer.class, resultVariable, target, arguments);
22 }
23
24 @Override
25 protected Integer zero() {
26 return 0;
27 }
28
29 @Override
30 protected Integer one() {
31 return 1;
32 }
33
34 @Override
35 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
36 return new CountLowerBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
37 substitutedArguments);
38 }
39
40 @Override
41 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
42 return new CountLowerBoundLiteral(getResultVariable(), newTarget, newArguments);
43 }
44
45 @Override
46 protected String operatorName() {
47 return "@LowerBound(\"partial\") count";
48 }
49}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java
new file mode 100644
index 00000000..03842143
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java
@@ -0,0 +1,51 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.representation.cardinality.UpperCardinalities;
16import tools.refinery.store.representation.cardinality.UpperCardinality;
17
18import java.util.List;
19
20public class CountUpperBoundLiteral extends AbstractCountLiteral<UpperCardinality> {
21 public CountUpperBoundLiteral(DataVariable<UpperCardinality> resultVariable, Constraint target,
22 List<Variable> arguments) {
23 super(UpperCardinality.class, resultVariable, target, arguments);
24 }
25
26 @Override
27 protected UpperCardinality zero() {
28 return UpperCardinalities.ZERO;
29 }
30
31 @Override
32 protected UpperCardinality one() {
33 return UpperCardinalities.UNBOUNDED;
34 }
35
36 @Override
37 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
38 return new CountUpperBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(),
39 substitutedArguments);
40 }
41
42 @Override
43 public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) {
44 return new CountUpperBoundLiteral(getResultVariable(), newTarget, newArguments);
45 }
46
47 @Override
48 protected String operatorName() {
49 return "@UpperBound(\"partial\") count";
50 }
51}
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
index 4e5a6099..2235a95d 100644
--- 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
@@ -6,18 +6,29 @@
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Reduction; 11import tools.refinery.store.query.literal.Reduction;
11import tools.refinery.store.query.term.Parameter; 12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.view.AnySymbolView;
12 14
13import java.util.List; 15import java.util.List;
14 16
15public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint { 17public record ModalConstraint(Modality modality, Concreteness concreteness, Constraint constraint)
16 private static final String FORMAT = "%s %s"; 18 implements Constraint {
19 public ModalConstraint {
20 if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) {
21 throw new InvalidQueryException("Already concrete constraints cannot be abstracted");
22 }
23 }
24
25 public ModalConstraint(Modality modality, Constraint constraint) {
26 this(modality, Concreteness.PARTIAL, constraint);
27 }
17 28
18 @Override 29 @Override
19 public String name() { 30 public String name() {
20 return FORMAT.formatted(modality, constraint.name()); 31 return formatName(constraint.name());
21 } 32 }
22 33
23 @Override 34 @Override
@@ -36,16 +47,33 @@ public record ModalConstraint(Modality modality, Constraint constraint) implemen
36 return false; 47 return false;
37 } 48 }
38 var otherModalConstraint = (ModalConstraint) other; 49 var otherModalConstraint = (ModalConstraint) other;
39 return modality == otherModalConstraint.modality && constraint.equals(helper, otherModalConstraint.constraint); 50 return modality == otherModalConstraint.modality &&
51 concreteness == otherModalConstraint.concreteness &&
52 constraint.equals(helper, otherModalConstraint.constraint);
40 } 53 }
41 54
42 @Override 55 @Override
43 public String toReferenceString() { 56 public String toReferenceString() {
44 return FORMAT.formatted(modality, constraint.toReferenceString()); 57 return formatName(constraint.toReferenceString());
45 } 58 }
46 59
47 @Override 60 @Override
48 public String toString() { 61 public String toString() {
49 return FORMAT.formatted(modality, constraint); 62 return formatName(constraint.toString());
63 }
64
65 private String formatName(String constraintName) {
66 if (concreteness == Concreteness.PARTIAL) {
67 return "%s %s".formatted(modality, constraintName);
68 }
69 return "%s %s %s".formatted(modality, concreteness, constraintName);
70 }
71
72 public static Constraint of(Modality modality, Concreteness concreteness, Constraint constraint) {
73 if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) {
74 // Symbol views and lifted constraints are already concrete. Thus, they cannot be abstracted at all.
75 return constraint;
76 }
77 return new ModalConstraint(modality, concreteness, constraint);
50 } 78 }
51} 79}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java
index 96466d5c..c99a0399 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java
@@ -11,14 +11,12 @@ import java.util.Locale;
11 11
12public enum Modality { 12public enum Modality {
13 MUST, 13 MUST,
14 MAY, 14 MAY;
15 CURRENT;
16 15
17 public Modality negate() { 16 public Modality negate() {
18 return switch(this) { 17 return switch(this) {
19 case MUST -> MAY; 18 case MUST -> MAY;
20 case MAY -> MUST; 19 case MAY -> MUST;
21 case CURRENT -> CURRENT;
22 }; 20 };
23 } 21 }
24 22
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
index 0e46a795..2614c26e 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallLiteral; 9import tools.refinery.store.query.literal.CallLiteral;
9 10
10public final class PartialLiterals { 11public final class PartialLiterals {
@@ -13,24 +14,28 @@ public final class PartialLiterals {
13 } 14 }
14 15
15 public static CallLiteral may(CallLiteral literal) { 16 public static CallLiteral may(CallLiteral literal) {
16 return addModality(literal, Modality.MAY); 17 return addModality(literal, Modality.MAY, Concreteness.PARTIAL);
17 } 18 }
18 19
19 public static CallLiteral must(CallLiteral literal) { 20 public static CallLiteral must(CallLiteral literal) {
20 return addModality(literal, Modality.MUST); 21 return addModality(literal, Modality.MUST, Concreteness.PARTIAL);
21 } 22 }
22 23
23 public static CallLiteral current(CallLiteral literal) { 24 public static CallLiteral candidateMay(CallLiteral literal) {
24 return addModality(literal, Modality.CURRENT); 25 return addModality(literal, Modality.MAY, Concreteness.CANDIDATE);
25 } 26 }
26 27
27 public static CallLiteral addModality(CallLiteral literal, Modality modality) { 28 public static CallLiteral candidateMust(CallLiteral literal) {
29 return addModality(literal, Modality.MUST, Concreteness.CANDIDATE);
30 }
31
32 public static CallLiteral addModality(CallLiteral literal, Modality modality, Concreteness concreteness) {
28 var target = literal.getTarget(); 33 var target = literal.getTarget();
29 if (target instanceof ModalConstraint) { 34 if (target instanceof ModalConstraint) {
30 throw new IllegalArgumentException("Literal %s already has modality".formatted(literal)); 35 throw new InvalidQueryException("Literal %s already has modality".formatted(literal));
31 } 36 }
32 var polarity = literal.getPolarity(); 37 var polarity = literal.getPolarity();
33 var modalTarget = new ModalConstraint(modality.commute(polarity), target); 38 var modalTarget = new ModalConstraint(modality.commute(polarity), concreteness, target);
34 return new CallLiteral(polarity, modalTarget, literal.getArguments()); 39 return new CallLiteral(polarity, modalTarget, literal.getArguments());
35 } 40 }
36} 41}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java
new file mode 100644
index 00000000..a7fc5b7e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java
@@ -0,0 +1,29 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10
11public abstract class AbstractPartialInterpretationRefiner<A, C> implements PartialInterpretationRefiner<A, C> {
12 private final ReasoningAdapter adapter;
13 private final PartialSymbol<A, C> partialSymbol;
14
15 protected AbstractPartialInterpretationRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol) {
16 this.adapter = adapter;
17 this.partialSymbol = partialSymbol;
18 }
19
20 @Override
21 public ReasoningAdapter getAdapter() {
22 return adapter;
23 }
24
25 @Override
26 public PartialSymbol<A, C> getPartialSymbol() {
27 return partialSymbol;
28 }
29}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java
new file mode 100644
index 00000000..6c381511
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10
11public sealed interface AnyPartialInterpretationRefiner permits PartialInterpretationRefiner {
12 ReasoningAdapter getAdapter();
13
14 AnyPartialSymbol getPartialSymbol();
15}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java
new file mode 100644
index 00000000..ebb9b824
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16public class ConcreteSymbolRefiner<A, C> extends AbstractPartialInterpretationRefiner<A, C> {
17 private final Interpretation<A> interpretation;
18
19 public ConcreteSymbolRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol,
20 Symbol<A> concreteSymbol) {
21 super(adapter, partialSymbol);
22 interpretation = adapter.getModel().getInterpretation(concreteSymbol);
23 }
24
25 @Override
26 public boolean merge(Tuple key, A value) {
27 var currentValue = interpretation.get(key);
28 var mergedValue = getPartialSymbol().abstractDomain().commonRefinement(currentValue, value);
29 if (!Objects.equals(currentValue, mergedValue)) {
30 interpretation.put(key, mergedValue);
31 }
32 return true;
33 }
34
35 public static <A1, C1> Factory<A1, C1> of(Symbol<A1> concreteSymbol) {
36 return (adapter, partialSymbol) -> new ConcreteSymbolRefiner<>(adapter, partialSymbol, concreteSymbol);
37 }
38}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java
new file mode 100644
index 00000000..d4ec2e8b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java
@@ -0,0 +1,79 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13public class DefaultStorageRefiner<T> implements StorageRefiner {
14 private static final StorageRefiner.Factory<Object> FACTORY = DefaultStorageRefiner::new;
15
16 private final Interpretation<T> interpretation;
17
18 public DefaultStorageRefiner(Symbol<T> symbol, Model model) {
19 interpretation = model.getInterpretation(symbol);
20 }
21
22 @Override
23 public boolean split(int parentNode, int childNode) {
24 var symbol = interpretation.getSymbol();
25 int arity = symbol.arity();
26 for (int i = 0; i < arity; i++) {
27 int adjacentSize = interpretation.getAdjacentSize(i, parentNode);
28 if (adjacentSize == 0) {
29 continue;
30 }
31 var toSetKeys = new Tuple[adjacentSize];
32 // This is safe, because we won't pass the array to the outside.
33 @SuppressWarnings("unchecked")
34 var toSetValues = (T[]) new Object[adjacentSize];
35 var cursor = interpretation.getAdjacent(i, parentNode);
36 int j = 0;
37 while (cursor.move()) {
38 toSetKeys[j] = cursor.getKey().set(i, childNode);
39 toSetValues[j] = cursor.getValue();
40 j++;
41 }
42 for (j = 0; j < adjacentSize; j++) {
43 interpretation.put(toSetKeys[j], toSetValues[j]);
44 }
45 }
46 return true;
47 }
48
49 @Override
50 public boolean cleanup(int nodeToDelete) {
51 var symbol = interpretation.getSymbol();
52 int arity = symbol.arity();
53 var defaultValue = symbol.defaultValue();
54 for (int i = 0; i < arity; i++) {
55 int adjacentSize = interpretation.getAdjacentSize(i, nodeToDelete);
56 if (adjacentSize == 0) {
57 continue;
58 }
59 var toDelete = new Tuple[adjacentSize];
60 var cursor = interpretation.getAdjacent(i, nodeToDelete);
61 int j = 0;
62 while (cursor.move()) {
63 toDelete[j] = cursor.getKey();
64 j++;
65 }
66 for (j = 0; j < adjacentSize; j++) {
67 interpretation.put(toDelete[j], defaultValue);
68 }
69 }
70 return true;
71 }
72
73 public static <T> StorageRefiner.Factory<T> factory() {
74 // This is safe, because {@code FACTORY} doesn't depend on {@code T} at all.
75 @SuppressWarnings("unchecked")
76 var typedFactory = (StorageRefiner.Factory<T>) FACTORY;
77 return typedFactory;
78 }
79}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java
new file mode 100644
index 00000000..f48d1d1f
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10import tools.refinery.store.tuple.Tuple;
11
12public non-sealed interface PartialInterpretationRefiner<A, C> extends AnyPartialInterpretationRefiner {
13 @Override
14 PartialSymbol<A, C> getPartialSymbol();
15
16 boolean merge(Tuple key, A value);
17
18 @FunctionalInterface
19 interface Factory<A, C> {
20 PartialInterpretationRefiner<A, C> create(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol);
21 }
22}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java
new file mode 100644
index 00000000..0c82695c
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.seed.ModelSeed;
10
11@FunctionalInterface
12public interface PartialModelInitializer {
13 void initialize(Model model, ModelSeed modelSeed);
14}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java
new file mode 100644
index 00000000..b6bccb01
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java
@@ -0,0 +1,34 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12
13public class RefinementBasedInitializer<A, C> implements PartialModelInitializer {
14 private final PartialSymbol<A, C> partialSymbol;
15
16 public RefinementBasedInitializer(PartialSymbol<A, C> partialSymbol) {
17 this.partialSymbol = partialSymbol;
18 }
19
20 @Override
21 public void initialize(Model model, ModelSeed modelSeed) {
22 var refiner = model.getAdapter(ReasoningAdapter.class).getRefiner(partialSymbol);
23 var defaultValue = partialSymbol.abstractDomain().unknown();
24 var cursor = modelSeed.getCursor(partialSymbol, defaultValue);
25 while (cursor.move()) {
26 var key = cursor.getKey();
27 var value = cursor.getValue();
28 if (!refiner.merge(key, value)) {
29 throw new IllegalArgumentException("Failed to merge value %s for key %s into %s"
30 .formatted(value, key, partialSymbol));
31 }
32 }
33 }
34}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementResult.java
new file mode 100644
index 00000000..1bc537d1
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementResult.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8public enum RefinementResult {
9 UNCHANGED,
10 REFINED,
11 REJECTED;
12
13 public RefinementResult andThen(RefinementResult next) {
14 return switch (this) {
15 case UNCHANGED -> next;
16 case REFINED -> next == REJECTED ? REJECTED : REFINED;
17 case REJECTED -> REJECTED;
18 };
19 }
20
21 public boolean isRejected() {
22 return this == REJECTED;
23 }
24}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java
new file mode 100644
index 00000000..004696fd
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java
@@ -0,0 +1,20 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.representation.Symbol;
10
11public interface StorageRefiner {
12 boolean split(int parentNode, int childNode);
13
14 boolean cleanup(int nodeToDelete);
15
16 @FunctionalInterface
17 interface Factory<T> {
18 StorageRefiner create(Symbol<T> symbol, Model model);
19 }
20}
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
index d58d026f..e59c8af8 100644
--- 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
@@ -15,11 +15,6 @@ public record PartialFunction<A, C>(String name, int arity, AbstractDomain<A, C>
15 } 15 }
16 16
17 @Override 17 @Override
18 public C defaultConcreteValue() {
19 return null;
20 }
21
22 @Override
23 public boolean equals(Object o) { 18 public boolean equals(Object o) {
24 return this == o; 19 return this == o;
25 } 20 }
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java
index 6b2f050b..4ccb7033 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java
@@ -26,11 +26,6 @@ public record PartialRelation(String name, int arity) implements PartialSymbol<T
26 } 26 }
27 27
28 @Override 28 @Override
29 public Boolean defaultConcreteValue() {
30 return false;
31 }
32
33 @Override
34 public List<Parameter> getParameters() { 29 public List<Parameter> getParameters() {
35 var parameters = new Parameter[arity]; 30 var parameters = new Parameter[arity];
36 Arrays.fill(parameters, Parameter.NODE_OUT); 31 Arrays.fill(parameters, Parameter.NODE_OUT);
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
index 3a08bdd8..38b2e466 100644
--- 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
@@ -13,5 +13,11 @@ public sealed interface PartialSymbol<A, C> extends AnyPartialSymbol permits Par
13 13
14 A defaultValue(); 14 A defaultValue();
15 15
16 C defaultConcreteValue(); 16 static PartialRelation of(String name, int arity) {
17 return new PartialRelation(name, arity);
18 }
19
20 static <A, C> PartialFunction<A, C> of(String name, int arity, AbstractDomain<A, C> abstractDomain) {
21 return new PartialFunction<>(name, arity, abstractDomain);
22 }
17} 23}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java
new file mode 100644
index 00000000..8b1c3bb3
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java
@@ -0,0 +1,111 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Map;
13import java.util.Objects;
14
15record MapBasedSeed<T>(int arity, Class<T> valueType, T reducedValue, Map<Tuple, T> map) implements Seed<T> {
16 @Override
17 public T get(Tuple key) {
18 var value = map.get(key);
19 return value == null ? reducedValue : value;
20 }
21
22 @Override
23 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
24 if (Objects.equals(defaultValue, reducedValue)) {
25 return Cursors.of(map);
26 }
27 return new CartesianProductCursor<>(arity, nodeCount, reducedValue, defaultValue, map);
28 }
29
30 private static class CartesianProductCursor<T> implements Cursor<Tuple, T> {
31 private final int nodeCount;
32 private final T reducedValue;
33 private final T defaultValue;
34 private final Map<Tuple, T> map;
35 private final int[] counter;
36 private State state = State.INITIAL;
37 private Tuple key;
38 private T value;
39
40 private CartesianProductCursor(int arity, int nodeCount, T reducedValue, T defaultValue, Map<Tuple, T> map) {
41 this.nodeCount = nodeCount;
42 this.reducedValue = reducedValue;
43 this.defaultValue = defaultValue;
44 this.map = map;
45 counter = new int[arity];
46 }
47
48 @Override
49 public Tuple getKey() {
50 return key;
51 }
52
53 @Override
54 public T getValue() {
55 return value;
56 }
57
58 @Override
59 public boolean isTerminated() {
60 return state == State.TERMINATED;
61 }
62
63 @Override
64 public boolean move() {
65 return switch (state) {
66 case INITIAL -> {
67 state = State.STARTED;
68 yield checkValue() || moveToNext();
69 }
70 case STARTED -> moveToNext();
71 case TERMINATED -> false;
72 };
73 }
74
75 private boolean moveToNext() {
76 do {
77 increment();
78 } while (state != State.TERMINATED && !checkValue());
79 return state != State.TERMINATED;
80 }
81
82 private void increment() {
83 int i = counter.length - 1;
84 while (i >= 0) {
85 counter[i]++;
86 if (counter[i] < nodeCount) {
87 return;
88 }
89 counter[i] = 0;
90 i--;
91 }
92 state = State.TERMINATED;
93 }
94
95 private boolean checkValue() {
96 key = Tuple.of(counter);
97 var valueInMap = map.get(key);
98 if (Objects.equals(valueInMap, defaultValue)) {
99 return false;
100 }
101 value = valueInMap == null ? reducedValue : valueInMap;
102 return true;
103 }
104
105 private enum State {
106 INITIAL,
107 STARTED,
108 TERMINATED
109 }
110 }
111}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java
new file mode 100644
index 00000000..e6b3eaf9
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java
@@ -0,0 +1,95 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Collections;
14import java.util.LinkedHashMap;
15import java.util.Map;
16import java.util.Set;
17import java.util.function.Consumer;
18
19public class ModelSeed {
20 private final int nodeCount;
21 private final Map<AnyPartialSymbol, Seed<?>> seeds;
22
23 private ModelSeed(int nodeCount, Map<AnyPartialSymbol, Seed<?>> seeds) {
24 this.nodeCount = nodeCount;
25 this.seeds = seeds;
26 }
27
28 public int getNodeCount() {
29 return nodeCount;
30 }
31
32 public <A> Seed<A> getSeed(PartialSymbol<A, ?> partialSymbol) {
33 var seed = seeds.get(partialSymbol);
34 if (seed == null) {
35 throw new IllegalArgumentException("No seed for partial symbol " + partialSymbol);
36 }
37 // The builder makes sure only well-typed seeds can be added.
38 @SuppressWarnings("unchecked")
39 var typedSeed = (Seed<A>) seed;
40 return typedSeed;
41 }
42
43 public boolean containsSeed(AnyPartialSymbol symbol) {
44 return seeds.containsKey(symbol);
45 }
46
47 public Set<AnyPartialSymbol> getSeededSymbols() {
48 return Collections.unmodifiableSet(seeds.keySet());
49 }
50
51 public <A> Cursor<Tuple, A> getCursor(PartialSymbol<A, ?> partialSymbol, A defaultValue) {
52 return getSeed(partialSymbol).getCursor(defaultValue, nodeCount);
53 }
54
55 public static Builder builder(int nodeCount) {
56 return new Builder(nodeCount);
57 }
58
59 public static class Builder {
60 private final int nodeCount;
61 private final Map<AnyPartialSymbol, Seed<?>> seeds = new LinkedHashMap<>();
62
63 private Builder(int nodeCount) {
64 if (nodeCount < 0) {
65 throw new IllegalArgumentException("Node count must not be negative");
66 }
67 this.nodeCount = nodeCount;
68 }
69
70 public <A> Builder seed(PartialSymbol<A, ?> partialSymbol, Seed<A> seed) {
71 if (seed.arity() != partialSymbol.arity()) {
72 throw new IllegalStateException("Expected seed of arity %d for partial symbol %s, but got %d instead"
73 .formatted(partialSymbol.arity(), partialSymbol, seed.arity()));
74 }
75 if (!seed.valueType().equals(partialSymbol.abstractDomain().abstractType())) {
76 throw new IllegalStateException("Expected seed of type %s for partial symbol %s, but got %s instead"
77 .formatted(partialSymbol.abstractDomain().abstractType(), partialSymbol, seed.valueType()));
78 }
79 if (seeds.put(partialSymbol, seed) != null) {
80 throw new IllegalArgumentException("Duplicate seed for partial symbol " + partialSymbol);
81 }
82 return this;
83 }
84
85 public <A> Builder seed(PartialSymbol<A, ?> partialSymbol, Consumer<Seed.Builder<A>> callback) {
86 var builder = Seed.builder(partialSymbol);
87 callback.accept(builder);
88 return seed(partialSymbol, builder.build());
89 }
90
91 public ModelSeed build() {
92 return new ModelSeed(nodeCount, Collections.unmodifiableMap(seeds));
93 }
94 }
95}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
index 08079f12..732efbcc 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
@@ -6,14 +6,73 @@
6package tools.refinery.store.reasoning.seed; 6package tools.refinery.store.reasoning.seed;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
10 12
13import java.util.Collections;
14import java.util.LinkedHashMap;
15import java.util.Map;
16
11public interface Seed<T> { 17public interface Seed<T> {
12 int arity(); 18 int arity();
13 19
20 Class<T> valueType();
21
14 T reducedValue(); 22 T reducedValue();
15 23
16 T get(Tuple key); 24 T get(Tuple key);
17 25
18 Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount); 26 Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount);
27
28 static <T> Builder<T> builder(int arity, Class<T> valueType, T reducedValue) {
29 return new Builder<>(arity, valueType, reducedValue);
30 }
31
32 static <T> Builder<T> builder(Symbol<T> symbol) {
33 return builder(symbol.arity(), symbol.valueType(), symbol.defaultValue());
34 }
35
36 static <T> Builder<T> builder(PartialSymbol<T, ?> partialSymbol) {
37 return builder(partialSymbol.arity(), partialSymbol.abstractDomain().abstractType(),
38 partialSymbol.defaultValue());
39 }
40
41 @SuppressWarnings("UnusedReturnValue")
42 class Builder<T> {
43 private final int arity;
44 private final Class<T> valueType;
45 private T reducedValue;
46 private final Map<Tuple, T> map = new LinkedHashMap<>();
47
48 private Builder(int arity, Class<T> valueType, T reducedValue) {
49 this.arity = arity;
50 this.valueType = valueType;
51 this.reducedValue = reducedValue;
52 }
53
54 public Builder<T> reducedValue(T reducedValue) {
55 this.reducedValue = reducedValue;
56 return this;
57 }
58
59 public Builder<T> put(Tuple key, T value) {
60 if (key.getSize() != arity) {
61 throw new IllegalArgumentException("Expected %s to have %d elements".formatted(key, arity));
62 }
63 map.put(key, value);
64 return this;
65 }
66
67 public Builder<T> putAll(Map<Tuple, T> map) {
68 for (var entry : map.entrySet()) {
69 put(entry.getKey(), entry.getValue());
70 }
71 return this;
72 }
73
74 public Seed<T> build() {
75 return new MapBasedSeed<>(arity, valueType, reducedValue, Collections.unmodifiableMap(map));
76 }
77 }
19} 78}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java
new file mode 100644
index 00000000..9af457d8
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.Symbol;
12
13public class SeedInitializer<T> implements PartialModelInitializer {
14 private final Symbol<T> symbol;
15 private final PartialSymbol<T, ?> partialSymbol;
16
17 public SeedInitializer(Symbol<T> symbol, PartialSymbol<T, ?> partialSymbol) {
18 this.symbol = symbol;
19 this.partialSymbol = partialSymbol;
20 }
21
22 @Override
23 public void initialize(Model model, ModelSeed modelSeed) {
24 var interpretation = model.getInterpretation(symbol);
25 var cursor = modelSeed.getCursor(partialSymbol, symbol.defaultValue());
26 interpretation.putAll(cursor);
27 }
28}
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
deleted file mode 100644
index 451d1513..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java
+++ /dev/null
@@ -1,27 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.tuple.Tuple;
10
11public record UniformSeed<T>(int arity, T reducedValue) implements Seed<T> {
12 public UniformSeed {
13 if (arity < 0) {
14 throw new IllegalArgumentException("Arity must not be negative");
15 }
16 }
17
18 @Override
19 public T get(Tuple key) {
20 return reducedValue;
21 }
22
23 @Override
24 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
25 return null;
26 }
27}
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
deleted file mode 100644
index d6a9e02c..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java
+++ /dev/null
@@ -1,159 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.*;
15
16public final class Advice {
17 private final AnyPartialSymbol source;
18 private final PartialRelation target;
19 private final AdviceSlot slot;
20 private final boolean mandatory;
21 private final List<Variable> parameters;
22 private final List<Literal> literals;
23 private boolean processed;
24
25 public Advice(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot, boolean mandatory, List<Variable> parameters, List<Literal> literals) {
26 if (mandatory && !slot.isMonotonic()) {
27 throw new IllegalArgumentException("Only monotonic advice can be mandatory");
28 }
29 this.source = source;
30 this.target = target;
31 this.slot = slot;
32 this.mandatory = mandatory;
33 checkArity(parameters);
34 this.parameters = parameters;
35 this.literals = literals;
36 }
37
38 public AnyPartialSymbol source() {
39 return source;
40 }
41
42 public PartialRelation target() {
43 return target;
44 }
45
46 public AdviceSlot slot() {
47 return slot;
48 }
49
50 public boolean mandatory() {
51 return mandatory;
52 }
53
54 public List<Variable> parameters() {
55 return parameters;
56 }
57
58 public List<Literal> literals() {
59 return literals;
60 }
61
62 public boolean processed() {
63 return processed;
64 }
65
66 public List<Literal> substitute(List<Variable> substituteParameters) {
67 checkArity(substituteParameters);
68 markProcessed();
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 = Substitution.builder().putManyChecked(parameters, substituteParameters).renewing().build();
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
deleted file mode 100644
index bab20340..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java
+++ /dev/null
@@ -1,30 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.representation.TruthValue;
9
10public enum AdviceSlot {
11 EXTEND_MUST(true),
12
13 RESTRICT_MAY(true),
14
15 /**
16 * Same as {@link #RESTRICT_MAY}, but only active if the value of the relation is not {@link TruthValue#TRUE} or
17 * {@link TruthValue#ERROR}.
18 */
19 RESTRICT_NEW(false);
20
21 private final boolean monotonic;
22
23 AdviceSlot(boolean monotonic) {
24 this.monotonic = monotonic;
25 }
26
27 public boolean isMonotonic() {
28 return monotonic;
29 }
30}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java
new file mode 100644
index 00000000..48c84348
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11
12public sealed interface AnyPartialSymbolTranslator extends ModelStoreConfiguration permits PartialSymbolTranslator {
13 AnyPartialSymbol getPartialSymbol();
14
15 void configure(ModelStoreBuilder storeBuilder);
16}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java
new file mode 100644
index 00000000..c2039afc
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java
@@ -0,0 +1,390 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.dse.transition.objectives.Criteria;
10import tools.refinery.store.dse.transition.objectives.Criterion;
11import tools.refinery.store.dse.transition.objectives.Objective;
12import tools.refinery.store.dse.transition.objectives.Objectives;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.query.Constraint;
15import tools.refinery.store.query.dnf.Query;
16import tools.refinery.store.query.dnf.QueryBuilder;
17import tools.refinery.store.query.dnf.RelationalQuery;
18import tools.refinery.store.query.literal.Literal;
19import tools.refinery.store.query.term.NodeVariable;
20import tools.refinery.store.query.view.MayView;
21import tools.refinery.store.query.view.MustView;
22import tools.refinery.store.reasoning.ReasoningAdapter;
23import tools.refinery.store.reasoning.ReasoningBuilder;
24import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
25import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
26import tools.refinery.store.reasoning.interpretation.QueryBasedRelationInterpretationFactory;
27import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter;
28import tools.refinery.store.reasoning.lifting.DnfLifter;
29import tools.refinery.store.reasoning.literal.Concreteness;
30import tools.refinery.store.reasoning.literal.Modality;
31import tools.refinery.store.reasoning.literal.PartialLiterals;
32import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
33import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
34import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
35import tools.refinery.store.reasoning.refinement.StorageRefiner;
36import tools.refinery.store.reasoning.representation.PartialRelation;
37import tools.refinery.store.representation.AnySymbol;
38import tools.refinery.store.representation.Symbol;
39import tools.refinery.store.representation.TruthValue;
40
41import java.util.ArrayList;
42import java.util.function.BiConsumer;
43
44import static tools.refinery.store.query.literal.Literals.not;
45
46@SuppressWarnings("UnusedReturnValue")
47public final class PartialRelationTranslator extends PartialSymbolTranslator<TruthValue, Boolean> {
48 private final PartialRelation partialRelation;
49 private PartialRelationRewriter rewriter;
50 private RelationalQuery query;
51 private RelationalQuery may;
52 private RelationalQuery must;
53 private RelationalQuery candidateMay;
54 private RelationalQuery candidateMust;
55 private RoundingMode roundingMode;
56
57 private PartialRelationTranslator(PartialRelation partialRelation) {
58 super(partialRelation);
59 this.partialRelation = partialRelation;
60 }
61
62 public PartialRelation getPartialRelation() {
63 return partialRelation;
64 }
65
66 @Override
67 public PartialRelationTranslator symbol(AnySymbol storageSymbol) {
68 super.symbol(storageSymbol);
69 return this;
70 }
71
72 @Override
73 public <T> PartialRelationTranslator symbol(Symbol<T> storageSymbol,
74 StorageRefiner.Factory<T> storageRefiner) {
75 super.symbol(storageSymbol, storageRefiner);
76 return this;
77 }
78
79 @Override
80 public PartialRelationTranslator interpretation(
81 PartialInterpretation.Factory<TruthValue, Boolean> interpretationFactory) {
82 super.interpretation(interpretationFactory);
83 return this;
84 }
85
86 @Override
87 public PartialRelationTranslator refiner(
88 PartialInterpretationRefiner.Factory<TruthValue, Boolean> interpretationRefiner) {
89 super.refiner(interpretationRefiner);
90 return this;
91 }
92
93 public PartialRelationTranslator rewriter(PartialRelationRewriter rewriter) {
94 checkNotConfigured();
95 if (this.rewriter != null) {
96 throw new IllegalArgumentException("Rewriter was already set");
97 }
98 this.rewriter = rewriter;
99 return this;
100 }
101
102 @Override
103 public PartialRelationTranslator initializer(PartialModelInitializer initializer) {
104 super.initializer(initializer);
105 return this;
106 }
107
108 @Override
109 public PartialRelationTranslator decision(Rule decisionRule) {
110 super.decision(decisionRule);
111 return this;
112 }
113
114 @Override
115 public PartialRelationTranslator accept(Criterion acceptanceCriterion) {
116 super.accept(acceptanceCriterion);
117 return this;
118 }
119
120 @Override
121 public PartialRelationTranslator exclude(Criterion exclusionCriterion) {
122 super.exclude(exclusionCriterion);
123 return this;
124 }
125
126 @Override
127 public PartialRelationTranslator objective(Objective objective) {
128 super.objective(objective);
129 return this;
130 }
131
132 public PartialRelationTranslator query(RelationalQuery query) {
133 checkNotConfigured();
134 if (this.query != null) {
135 throw new IllegalArgumentException("Query was already set");
136 }
137 this.query = query;
138 return this;
139 }
140
141 public PartialRelationTranslator may(RelationalQuery may) {
142 checkNotConfigured();
143 if (this.may != null) {
144 throw new IllegalArgumentException("May query was already set");
145 }
146 this.may = may;
147 return this;
148 }
149
150 public PartialRelationTranslator mayNever() {
151 var never = createQuery(partialRelation.name() + "#never", (builder, parameters) -> {});
152 may(never);
153 return this;
154 }
155
156 public PartialRelationTranslator must(RelationalQuery must) {
157 checkNotConfigured();
158 if (this.must != null) {
159 throw new IllegalArgumentException("Must query was already set");
160 }
161 this.must = must;
162 return this;
163 }
164
165 public PartialRelationTranslator candidate(RelationalQuery candidate) {
166 candidateMay(candidate);
167 candidateMust(candidate);
168 return this;
169 }
170
171 public PartialRelationTranslator candidateMay(RelationalQuery candidateMay) {
172 checkNotConfigured();
173 if (this.candidateMay != null) {
174 throw new IllegalArgumentException("Candidate may query was already set");
175 }
176 this.candidateMay = candidateMay;
177 return this;
178 }
179
180 public PartialRelationTranslator candidateMust(RelationalQuery candidateMust) {
181 checkNotConfigured();
182 if (this.candidateMust != null) {
183 throw new IllegalArgumentException("Candidate must query was already set");
184 }
185 this.candidateMust = candidateMust;
186 return this;
187 }
188
189 public PartialRelationTranslator roundingMode(RoundingMode roundingMode) {
190 checkNotConfigured();
191 if (this.roundingMode != null) {
192 throw new IllegalArgumentException("Rounding mode was already set");
193 }
194 this.roundingMode = roundingMode;
195 return this;
196 }
197
198 @Override
199 protected void doConfigure(ModelStoreBuilder storeBuilder) {
200 setFallbackRoundingMode();
201 createFallbackQueryFromRewriter();
202 liftQueries(storeBuilder);
203 createFallbackQueriesFromSymbol();
204 setFallbackCandidateQueries();
205 createFallbackRewriter();
206 createFallbackInterpretation();
207 createFallbackRefiner();
208 createFallbackExclude();
209 createFallbackObjective();
210 super.doConfigure(storeBuilder);
211 }
212
213 private void setFallbackRoundingMode() {
214 if (roundingMode == null) {
215 roundingMode = query == null && storageSymbol != null ? RoundingMode.PREFER_FALSE : RoundingMode.NONE;
216 }
217 }
218
219 private RelationalQuery createQuery(String name, BiConsumer<QueryBuilder, NodeVariable[]> callback) {
220 int arity = partialRelation.arity();
221 var queryBuilder = Query.builder(name);
222 var parameters = new NodeVariable[arity];
223 for (int i = 0; i < arity; i++) {
224 parameters[i] = queryBuilder.parameter("p" + 1);
225 }
226 callback.accept(queryBuilder, parameters);
227 return queryBuilder.build();
228 }
229
230 private RelationalQuery createQuery(String name, Constraint constraint) {
231 return createQuery(name, (builder, parameters) -> builder.clause(constraint.call(parameters)));
232 }
233
234 private void createFallbackQueryFromRewriter() {
235 if (rewriter != null && query == null) {
236 query = createQuery(partialRelation.name(), partialRelation);
237 }
238 }
239
240 private void createFallbackQueriesFromSymbol() {
241 if (storageSymbol == null || storageSymbol.valueType() != TruthValue.class) {
242 return;
243 }
244 // We checked in the guard clause that this is safe.
245 @SuppressWarnings("unchecked")
246 var typedStorageSymbol = (Symbol<TruthValue>) storageSymbol;
247 var defaultValue = typedStorageSymbol.defaultValue();
248 if (may == null && !defaultValue.may()) {
249 may = createQuery(DnfLifter.decorateName(partialRelation.name(), Modality.MAY, Concreteness.PARTIAL),
250 new MayView(typedStorageSymbol));
251 }
252 if (must == null && !defaultValue.must()) {
253 must = createQuery(DnfLifter.decorateName(partialRelation.name(), Modality.MUST, Concreteness.PARTIAL),
254 new MustView(typedStorageSymbol));
255 }
256 }
257
258 private void liftQueries(ModelStoreBuilder storeBuilder) {
259 if (rewriter instanceof QueryBasedRelationRewriter queryBasedRelationRewriter) {
260 liftQueriesFromQueryBasedRewriter(queryBasedRelationRewriter);
261 } else if (query != null) {
262 liftQueriesFromFourValuedQuery(storeBuilder);
263 }
264 }
265
266 private void liftQueriesFromQueryBasedRewriter(QueryBasedRelationRewriter queryBasedRelationRewriter) {
267 if (may == null) {
268 may = queryBasedRelationRewriter.getMay();
269 }
270 if (must == null) {
271 must = queryBasedRelationRewriter.getMust();
272 }
273 if (candidateMay == null) {
274 candidateMay = queryBasedRelationRewriter.getCandidateMay();
275 }
276 if (candidateMust == null) {
277 candidateMust = queryBasedRelationRewriter.getCandidateMust();
278 }
279 }
280
281 private void liftQueriesFromFourValuedQuery(ModelStoreBuilder storeBuilder) {
282 var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class);
283 if (may == null) {
284 may = reasoningBuilder.lift(Modality.MAY, Concreteness.PARTIAL, query);
285 }
286 if (must == null) {
287 must = reasoningBuilder.lift(Modality.MUST, Concreteness.PARTIAL, query);
288 }
289 if (candidateMay == null) {
290 candidateMay = reasoningBuilder.lift(Modality.MAY, Concreteness.CANDIDATE, query);
291 }
292 if (candidateMust == null) {
293 candidateMust = reasoningBuilder.lift(Modality.MAY, Concreteness.CANDIDATE, query);
294 }
295 }
296
297 private void setFallbackCandidateQueries() {
298 if (candidateMay == null) {
299 candidateMay = switch (roundingMode) {
300 case NONE, PREFER_TRUE -> may;
301 case PREFER_FALSE -> must;
302 };
303 }
304 if (candidateMust == null) {
305 candidateMust = switch (roundingMode) {
306 case NONE, PREFER_FALSE -> must;
307 case PREFER_TRUE -> may;
308 };
309 }
310 }
311
312 private void createFallbackRewriter() {
313 if (rewriter == null) {
314 rewriter = new QueryBasedRelationRewriter(may, must, candidateMay, candidateMust);
315 }
316 }
317
318 private void createFallbackInterpretation() {
319 if (interpretationFactory == null) {
320 interpretationFactory = new QueryBasedRelationInterpretationFactory(may, must, candidateMay, candidateMust);
321 }
322 }
323
324 private void createFallbackRefiner() {
325 if (interpretationRefiner == null && storageSymbol != null && storageSymbol.valueType() == TruthValue.class) {
326 // We checked in the condition that this is safe.
327 @SuppressWarnings("unchecked")
328 var typedStorageSymbol = (Symbol<TruthValue>) storageSymbol;
329 interpretationRefiner = ConcreteSymbolRefiner.of(typedStorageSymbol);
330 }
331 }
332
333 private void createFallbackExclude() {
334 if (excludeWasSet) {
335 return;
336 }
337 var excludeQuery = createQuery("exclude", (builder, parameters) -> {
338 var literals = new ArrayList<Literal>(parameters.length + 2);
339 literals.add(PartialLiterals.must(partialRelation.call(parameters)));
340 literals.add(not(PartialLiterals.may(partialRelation.call(parameters))));
341 for (var parameter : parameters) {
342 literals.add(PartialLiterals.must(ReasoningAdapter.EXISTS_SYMBOL.call(parameter)));
343 }
344 builder.clause(literals);
345 });
346 exclude = Criteria.whenHasMatch(excludeQuery);
347 }
348
349 private void createFallbackObjective() {
350 if (acceptWasSet && objectiveWasSet) {
351 return;
352 }
353 var invalidCandidate = createQuery("invalidCandidate", (builder, parameters) -> builder
354 .clause(
355 PartialLiterals.candidateMust(partialRelation.call(parameters)),
356 not(PartialLiterals.candidateMay(partialRelation.call(parameters)))
357 )
358 .clause(
359 PartialLiterals.candidateMust(partialRelation.call(parameters)),
360 not(PartialLiterals.may(partialRelation.call(parameters)))
361 )
362 .clause(
363 PartialLiterals.must(partialRelation.call(parameters)),
364 not(PartialLiterals.candidateMay(partialRelation.call(parameters)))
365 ));
366 var reject = createQuery("reject", (builder, parameters) -> {
367 var literals = new ArrayList<Literal>(parameters.length + 1);
368 literals.add(invalidCandidate.call(parameters));
369 for (var parameter : parameters) {
370 literals.add(PartialLiterals.candidateMust(ReasoningAdapter.EXISTS_SYMBOL.call(parameter)));
371 }
372 builder.clause(literals);
373 });
374 if (!acceptWasSet) {
375 accept = Criteria.whenNoMatch(reject);
376 }
377 if (!objectiveWasSet) {
378 objective = Objectives.count(reject);
379 }
380 }
381
382 public PartialRelationRewriter getRewriter() {
383 checkConfigured();
384 return rewriter;
385 }
386
387 public static PartialRelationTranslator of(PartialRelation relation) {
388 return new PartialRelationTranslator(relation);
389 }
390}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java
new file mode 100644
index 00000000..6cdb287d
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java
@@ -0,0 +1,212 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.Rule;
11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.Objective;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.reasoning.ReasoningBuilder;
15import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
16import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
17import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
18import tools.refinery.store.reasoning.refinement.StorageRefiner;
19import tools.refinery.store.reasoning.representation.PartialSymbol;
20import tools.refinery.store.reasoning.seed.SeedInitializer;
21import tools.refinery.store.representation.AnySymbol;
22import tools.refinery.store.representation.Symbol;
23
24import java.util.ArrayList;
25import java.util.List;
26
27@SuppressWarnings("UnusedReturnValue")
28public abstract sealed class PartialSymbolTranslator<A, C> implements AnyPartialSymbolTranslator
29 permits PartialRelationTranslator {
30 private final PartialSymbol<A, C> partialSymbol;
31 private boolean configured = false;
32 protected PartialInterpretationRefiner.Factory<A, C> interpretationRefiner;
33 protected AnySymbol storageSymbol;
34 protected StorageRefiner.Factory<?> storageRefiner;
35 protected PartialInterpretation.Factory<A, C> interpretationFactory;
36 protected PartialModelInitializer initializer;
37 protected List<Rule> decisionRules = new ArrayList<>();
38 protected boolean acceptWasSet;
39 protected @Nullable Criterion accept;
40 protected boolean excludeWasSet;
41 protected @Nullable Criterion exclude;
42 protected boolean objectiveWasSet;
43 protected @Nullable Objective objective;
44
45 PartialSymbolTranslator(PartialSymbol<A, C> partialSymbol) {
46 this.partialSymbol = partialSymbol;
47 }
48
49 @Override
50 public PartialSymbol<A, C> getPartialSymbol() {
51 return partialSymbol;
52 }
53
54 @Override
55 public void apply(ModelStoreBuilder storeBuilder) {
56 storeBuilder.getAdapter(ReasoningBuilder.class).partialSymbol(this);
57 }
58
59 public boolean isConfigured() {
60 return configured;
61 }
62
63 protected void checkConfigured() {
64 if (!configured) {
65 throw new IllegalStateException("Partial symbol was not configured");
66 }
67 }
68
69 protected void checkNotConfigured() {
70 if (configured) {
71 throw new IllegalStateException("Partial symbol was already configured");
72 }
73 }
74
75 public PartialSymbolTranslator<A, C> symbol(AnySymbol storageSymbol) {
76 return symbol((Symbol<?>) storageSymbol, null);
77 }
78
79 public <T> PartialSymbolTranslator<A, C> symbol(Symbol<T> storageSymbol,
80 StorageRefiner.Factory<T> storageRefiner) {
81 checkNotConfigured();
82 if (this.storageSymbol != null) {
83 throw new IllegalStateException("Representation symbol was already set");
84 }
85 this.storageSymbol = storageSymbol;
86 this.storageRefiner = storageRefiner;
87 return this;
88 }
89
90 public PartialSymbolTranslator<A, C> interpretation(PartialInterpretation.Factory<A, C> interpretationFactory) {
91 checkNotConfigured();
92 if (this.interpretationFactory != null) {
93 throw new IllegalStateException("Interpretation factory was already set");
94 }
95 this.interpretationFactory = interpretationFactory;
96 return this;
97 }
98
99 public PartialSymbolTranslator<A, C> refiner(PartialInterpretationRefiner.Factory<A, C> interpretationRefiner) {
100 checkNotConfigured();
101 if (this.interpretationRefiner != null) {
102 throw new IllegalStateException("Interpretation refiner was already set");
103 }
104 this.interpretationRefiner = interpretationRefiner;
105 return this;
106 }
107
108 public PartialSymbolTranslator<A, C> initializer(PartialModelInitializer initializer) {
109 checkNotConfigured();
110 if (this.initializer != null) {
111 throw new IllegalStateException("Initializer was already set");
112 }
113 this.initializer = initializer;
114 return this;
115 }
116
117 public PartialSymbolTranslator<A, C> decision(Rule decisionRule) {
118 decisionRules.add(decisionRule);
119 return this;
120 }
121
122 public PartialSymbolTranslator<A, C> accept(@Nullable Criterion acceptanceCriterion) {
123 if (acceptWasSet) {
124 throw new IllegalStateException("Accept was already set");
125 }
126 this.accept = acceptanceCriterion;
127 acceptWasSet = true;
128 return this;
129 }
130
131 public PartialSymbolTranslator<A, C> exclude(@Nullable Criterion exclusionCriterion) {
132 if (excludeWasSet) {
133 throw new IllegalStateException("Exclude was already set");
134 }
135 this.exclude = exclusionCriterion;
136 excludeWasSet = true;
137 return this;
138 }
139
140 public PartialSymbolTranslator<A, C> objective(Objective objective) {
141 if (objectiveWasSet) {
142 throw new IllegalStateException("Objective was already set");
143 }
144 this.objective = objective;
145 objectiveWasSet = true;
146 return this;
147 }
148
149 @Override
150 public void configure(ModelStoreBuilder storeBuilder) {
151 checkNotConfigured();
152 doConfigure(storeBuilder);
153 configured = true;
154 }
155
156 protected void doConfigure(ModelStoreBuilder storeBuilder) {
157 if (interpretationFactory == null) {
158 throw new IllegalArgumentException("Interpretation factory must be set");
159 }
160 var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class);
161 if (storageSymbol != null) {
162 storeBuilder.symbol(storageSymbol);
163 if (storageRefiner != null) {
164 registerStorageRefiner(reasoningBuilder, storageRefiner);
165 }
166 }
167 createFallbackInitializer();
168 if (initializer != null) {
169 reasoningBuilder.initializer(initializer);
170 }
171 storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class).ifPresent(dseBuilder -> {
172 dseBuilder.transformations(decisionRules);
173 if (accept != null) {
174 dseBuilder.accept(accept);
175 }
176 if (exclude != null) {
177 dseBuilder.exclude(exclude);
178 }
179 });
180 if (objective != null) {
181 reasoningBuilder.objective(objective);
182 }
183 }
184
185 private <T> void registerStorageRefiner(ReasoningBuilder reasoningBuilder, StorageRefiner.Factory<T> factory) {
186 // The builder only allows setting a well-typed representation refiner.
187 @SuppressWarnings("unchecked")
188 var typedStorageSymbol = (Symbol<T>) storageSymbol;
189 reasoningBuilder.storageRefiner(typedStorageSymbol, factory);
190 }
191
192 private void createFallbackInitializer() {
193 if (initializer == null &&
194 storageSymbol != null &&
195 storageSymbol.valueType().equals(partialSymbol.abstractDomain().abstractType())) {
196 // The guard clause makes this safe.
197 @SuppressWarnings("unchecked")
198 var typedStorageSymbol = (Symbol<A>) storageSymbol;
199 initializer = new SeedInitializer<>(typedStorageSymbol, partialSymbol);
200 }
201 }
202
203 public PartialInterpretation.Factory<A, C> getInterpretationFactory() {
204 checkConfigured();
205 return interpretationFactory;
206 }
207
208 public PartialInterpretationRefiner.Factory<A, C> getInterpretationRefiner() {
209 checkConfigured();
210 return interpretationRefiner;
211 }
212}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java
new file mode 100644
index 00000000..dd956a50
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8public enum RoundingMode {
9 NONE,
10 PREFER_TRUE,
11 PREFER_FALSE
12}
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
deleted file mode 100644
index 4a5a8843..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java
+++ /dev/null
@@ -1,27 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Modality;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.List;
18
19public interface TranslatedRelation {
20 PartialRelation getSource();
21
22 void configure(List<Advice> advices);
23
24 List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments);
25
26 PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model);
27}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java
new file mode 100644
index 00000000..edb886ba
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9
10public class TranslationException extends RuntimeException {
11 private final transient AnyPartialSymbol partialSymbol;
12
13 public TranslationException(AnyPartialSymbol partialSymbol) {
14 this.partialSymbol = partialSymbol;
15 }
16
17 public TranslationException(AnyPartialSymbol partialSymbol, String message) {
18 super(message);
19 this.partialSymbol = partialSymbol;
20 }
21
22 public TranslationException(AnyPartialSymbol partialSymbol, String message, Throwable cause) {
23 super(message, cause);
24 this.partialSymbol = partialSymbol;
25 }
26
27 public TranslationException(AnyPartialSymbol partialSymbol, Throwable cause) {
28 super(cause);
29 this.partialSymbol = partialSymbol;
30 }
31
32 public AnyPartialSymbol getPartialSymbol() {
33 return partialSymbol;
34 }
35}
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
deleted file mode 100644
index 6e44a7d7..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java
+++ /dev/null
@@ -1,32 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningBuilder;
10
11import java.util.Collection;
12
13public abstract class TranslationUnit {
14 private ReasoningBuilder reasoningBuilder;
15
16 protected ReasoningBuilder getReasoningBuilder() {
17 return reasoningBuilder;
18 }
19
20 public void setPartialInterpretationBuilder(ReasoningBuilder reasoningBuilder) {
21 this.reasoningBuilder = reasoningBuilder;
22 configureReasoningBuilder();
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
deleted file mode 100644
index 2a151aa2..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java
+++ /dev/null
@@ -1,93 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.resultset.ResultSet;
11import tools.refinery.store.reasoning.MergeResult;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningAdapter;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18public class BaseDecisionInterpretation implements PartialInterpretation<TruthValue, Boolean> {
19 private final ReasoningAdapter reasoningAdapter;
20 private PartialRelation partialRelation;
21 private final ResultSet<Boolean> mustResultSet;
22 private final ResultSet<Boolean> mayResultSet;
23 private final ResultSet<Boolean> errorResultSet;
24 private final ResultSet<Boolean> currentResultSet;
25 private final Interpretation<TruthValue> interpretation;
26
27 public BaseDecisionInterpretation(ReasoningAdapter reasoningAdapter, ResultSet<Boolean> mustResultSet,
28 ResultSet<Boolean> mayResultSet, ResultSet<Boolean> errorResultSet,
29 ResultSet<Boolean> currentResultSet, Interpretation<TruthValue> interpretation) {
30 this.reasoningAdapter = reasoningAdapter;
31 this.mustResultSet = mustResultSet;
32 this.mayResultSet = mayResultSet;
33 this.errorResultSet = errorResultSet;
34 this.currentResultSet = currentResultSet;
35 this.interpretation = interpretation;
36 }
37
38 @Override
39 public ReasoningAdapter getAdapter() {
40 return reasoningAdapter;
41 }
42
43 @Override
44 public int countUnfinished() {
45 return 0;
46 }
47
48 @Override
49 public int countErrors() {
50 return errorResultSet.size();
51 }
52
53 @Override
54 public PartialRelation getPartialSymbol() {
55 return partialRelation;
56 }
57
58 @Override
59 public TruthValue get(Tuple key) {
60 return null;
61 }
62
63 @Override
64 public Cursor<Tuple, TruthValue> getAll() {
65 return null;
66 }
67
68 @Override
69 public MergeResult merge(Tuple key, TruthValue value) {
70 TruthValue newValue;
71 switch (value) {
72 case UNKNOWN -> {
73 return MergeResult.UNCHANGED;
74 }
75 case TRUE -> newValue = mayResultSet.get(key) ? TruthValue.TRUE : TruthValue.ERROR;
76 case FALSE -> newValue = mustResultSet.get(key) ? TruthValue.ERROR : TruthValue.FALSE;
77 case ERROR -> newValue = TruthValue.ERROR;
78 default -> throw new IllegalArgumentException("Unknown truth value: " + value);
79 }
80 var oldValue = interpretation.put(key, newValue);
81 return oldValue == TruthValue.ERROR ? MergeResult.UNCHANGED : MergeResult.REFINED;
82 }
83
84 @Override
85 public Boolean getConcrete(Tuple key) {
86 return currentResultSet.get(key);
87 }
88
89 @Override
90 public Cursor<Tuple, Boolean> getAllConcrete() {
91 return null;
92 }
93}
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
deleted file mode 100644
index a1e4b816..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java
+++ /dev/null
@@ -1,49 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.seed.Seed;
11import tools.refinery.store.reasoning.seed.UniformSeed;
12import tools.refinery.store.reasoning.translator.TranslatedRelation;
13import tools.refinery.store.reasoning.translator.TranslationUnit;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.Collection;
18import java.util.List;
19
20public class BaseDecisionTranslationUnit extends TranslationUnit {
21 private final PartialRelation partialRelation;
22 private final Seed<TruthValue> seed;
23 private final Symbol<TruthValue> symbol;
24
25 public BaseDecisionTranslationUnit(PartialRelation partialRelation, Seed<TruthValue> seed) {
26 if (seed.arity() != partialRelation.arity()) {
27 throw new IllegalArgumentException("Expected seed with arity %d for %s, got arity %s"
28 .formatted(partialRelation.arity(), partialRelation, seed.arity()));
29 }
30 this.partialRelation = partialRelation;
31 this.seed = seed;
32 symbol = Symbol.of(partialRelation.name(), partialRelation.arity(), TruthValue.class, TruthValue.UNKNOWN);
33 }
34
35 public BaseDecisionTranslationUnit(PartialRelation partialRelation) {
36 this(partialRelation, new UniformSeed<>(partialRelation.arity(), TruthValue.UNKNOWN));
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
deleted file mode 100644
index 4782eb46..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java
+++ /dev/null
@@ -1,54 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningBuilder;
14import tools.refinery.store.reasoning.literal.Modality;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.translator.Advice;
17import tools.refinery.store.reasoning.translator.TranslatedRelation;
18import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.representation.TruthValue;
20
21import java.util.List;
22
23class TranslatedBaseDecision implements TranslatedRelation {
24 private final ReasoningBuilder reasoningBuilder;
25 private final PartialRelation partialRelation;
26 private final Symbol<TruthValue> symbol;
27
28 public TranslatedBaseDecision(ReasoningBuilder reasoningBuilder, PartialRelation partialRelation,
29 Symbol<TruthValue> symbol) {
30 this.reasoningBuilder = reasoningBuilder;
31 this.partialRelation = partialRelation;
32 this.symbol = symbol;
33 }
34
35 @Override
36 public PartialRelation getSource() {
37 return partialRelation;
38 }
39
40 @Override
41 public void configure(List<Advice> advices) {
42
43 }
44
45 @Override
46 public List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments) {
47 return null;
48 }
49
50 @Override
51 public PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model) {
52 return null;
53 }
54}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java
new file mode 100644
index 00000000..5c3298ac
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java
@@ -0,0 +1,255 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
9import tools.refinery.store.dse.transition.Rule;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.model.ModelStoreConfiguration;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.literal.Connectivity;
15import tools.refinery.store.query.literal.Literal;
16import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
17import tools.refinery.store.query.term.Variable;
18import tools.refinery.store.query.view.AnySymbolView;
19import tools.refinery.store.reasoning.lifting.DnfLifter;
20import tools.refinery.store.reasoning.literal.Concreteness;
21import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
22import tools.refinery.store.reasoning.literal.Modality;
23import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
24import tools.refinery.store.reasoning.representation.PartialRelation;
25import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
26import tools.refinery.store.reasoning.translator.RoundingMode;
27import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
28import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
29import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
30import tools.refinery.store.representation.Symbol;
31import tools.refinery.store.representation.cardinality.CardinalityIntervals;
32import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
33
34import java.util.ArrayList;
35import java.util.List;
36import java.util.Map;
37
38import static tools.refinery.store.query.literal.Literals.check;
39import static tools.refinery.store.query.literal.Literals.not;
40import static tools.refinery.store.query.term.int_.IntTerms.constant;
41import static tools.refinery.store.query.term.int_.IntTerms.less;
42import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL;
43import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
44import static tools.refinery.store.reasoning.actions.PartialActionLiterals.focus;
45import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
46
47public class ContainmentHierarchyTranslator implements ModelStoreConfiguration {
48 public static final PartialRelation CONTAINED_SYMBOL = new PartialRelation("contained", 1);
49 public static final PartialRelation INVALID_CONTAINER = new PartialRelation("invalidContainer",
50 1);
51 public static final PartialRelation CONTAINS_SYMBOL = new PartialRelation("contains", 2);
52
53 private final Symbol<InferredContainment> containsStorage = Symbol.of("CONTAINS", 2, InferredContainment.class,
54 InferredContainment.UNKNOWN);
55 private final AnySymbolView forbiddenContainsView = new ForbiddenContainsView(containsStorage);
56 private final RelationalQuery containsMayNewTargetHelper;
57 private final RelationalQuery containsMayExistingHelper;
58 private final RelationalQuery weakComponents;
59 private final RelationalQuery strongComponents;
60 private final Map<PartialRelation, ContainmentInfo> containmentInfoMap;
61
62 public ContainmentHierarchyTranslator(Map<PartialRelation, ContainmentInfo> containmentInfoMap) {
63 this.containmentInfoMap = containmentInfoMap;
64
65 var name = CONTAINS_SYMBOL.name();
66
67 containsMayNewTargetHelper = Query.of(name + "#mayNewTargetHelper", (builder, child) -> builder
68 .clause(Integer.class, existingContainers -> List.of(
69 may(CONTAINED_SYMBOL.call(child)),
70 new CountLowerBoundLiteral(existingContainers, CONTAINS_SYMBOL, List.of(Variable.of(), child)),
71 check(less(existingContainers, constant(1)))
72 )));
73
74 containsMayExistingHelper = Query.of(name + "#mayExistingHelper", (builder, parent, child) -> builder
75 .clause(Integer.class, existingContainers -> List.of(
76 must(CONTAINS_SYMBOL.call(parent, child)),
77 not(forbiddenContainsView.call(parent, child))
78 // Violation of monotonicity:
79 // Containment edges violating upper multiplicity will not be marked as {@code ERROR}, but the
80 // {@code invalidNumberOfContainers} error pattern will already mark the node as invalid.
81 )));
82
83 var mustExistBothContains = Query.of(name + "#mustExistBoth", (builder, parent, child) -> builder.clause(
84 must(CONTAINS_SYMBOL.call(parent, child)),
85 must(EXISTS_SYMBOL.call(parent)),
86 must(EXISTS_SYMBOL.call(child))
87 ));
88
89 weakComponents = Query.of(name + "#weakComponents", (builder, node, representative) -> builder.clause(
90 new RepresentativeElectionLiteral(Connectivity.WEAK, mustExistBothContains.getDnf(), node,
91 representative)
92 ));
93
94 strongComponents = Query.of(name + "#strongComponents", (builder, node, representative) -> builder.clause(
95 new RepresentativeElectionLiteral(Connectivity.STRONG, mustExistBothContains.getDnf(), node,
96 representative)
97 ));
98 }
99
100 @Override
101 public void apply(ModelStoreBuilder storeBuilder) {
102 storeBuilder.symbol(containsStorage);
103 translateContains(storeBuilder);
104 translateInvalidContainer(storeBuilder);
105 for (var entry : containmentInfoMap.entrySet()) {
106 var linkType = entry.getKey();
107 var info = entry.getValue();
108 translateContainmentLinkType(storeBuilder, linkType, info);
109 translateInvalidMultiplicity(storeBuilder, linkType, info);
110 }
111 translateFocusNotContained(storeBuilder);
112 }
113
114 private void translateContainmentLinkType(ModelStoreBuilder storeBuilder, PartialRelation linkType,
115 ContainmentInfo info) {
116 var name = linkType.name();
117 var sourceType = info.sourceType();
118 var targetType = info.targetType();
119 var upperCardinality = info.multiplicity().multiplicity().upperBound();
120
121 var mayNewSourceHelper = Query.of(name + "#mayNewSourceHelper", (builder, parent) -> {
122 var literals = new ArrayList<Literal>();
123 literals.add(may(sourceType.call(parent)));
124 if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) {
125 var existingCount = Variable.of("existingCount", Integer.class);
126 literals.add(new CountLowerBoundLiteral(existingCount, linkType, List.of(parent, Variable.of())));
127 literals.add(check(less(existingCount, constant(finiteUpperCardinality.finiteUpperBound()))));
128 }
129 builder.clause(literals);
130 });
131
132 var mayNewTargetHelper = Query.of(name + "#mayNewTargetHelper", (builder, child) -> builder.clause(
133 containsMayNewTargetHelper.call(child),
134 may(targetType.call(child))
135 ));
136
137 var forbiddenLinkView = new ForbiddenContainmentLinkView(containsStorage, linkType);
138
139 var mayNewHelper = Query.of(name + "#mayNewHelper", (builder, parent, child) -> builder.clause(
140 mayNewSourceHelper.call(parent),
141 mayNewTargetHelper.call(child),
142 not(must(CONTAINS_SYMBOL.call(parent, child))),
143 not(forbiddenLinkView.call(parent, child))
144 ));
145
146 var mayExistingHelper = Query.of(name + "#mayExistingHelper", (builder, parent, child) -> builder.clause(
147 must(linkType.call(parent, child)),
148 containsMayExistingHelper.call(parent, child),
149 may(sourceType.call(parent)),
150 may(targetType.call(child)),
151 not(forbiddenLinkView.call(parent, child))
152 // Violation of monotonicity:
153 // Containment edges violating upper multiplicity will not be marked as {@code ERROR}, but the
154 // {@code invalidNumberOfContainers} error pattern will already mark the node as invalid.
155 ));
156
157 var may = Query.of(name + "#may", (builder, parent, child) -> builder
158 .clause(
159 mayNewHelper.call(parent, child),
160 not(weakComponents.call(parent, Variable.of()))
161 )
162 .clause(representative -> List.of(
163 mayNewHelper.call(parent, child),
164 weakComponents.call(parent, representative),
165 // Violation of para-consistency:
166 // If there is a surely existing node with at least two containers, its (transitive) containers
167 // will end up in the same weakly connected component, and we will spuriously mark the
168 // containment edge between the (transitive) containers as {@code FALSE}. However, such
169 // models can never be finished.
170 //
171 // Violation of monotonicity:
172 // if the a {@code TRUE} value is added to the representation in the previous situation,
173 // we'll check strongly connected components instead of weakly connected ones. Therefore, the
174 // view for the partial symbol will change from {@code FALSE} to {@code TRUE}. This doesn't
175 // affect the overall inconsistency of the partial model (due to the surely existing node
176 // with multiple containers).
177 not(weakComponents.call(child, representative))
178 ))
179 .clause(
180 mayExistingHelper.call(parent, child),
181 not(strongComponents.call(parent, Variable.of()))
182 )
183 .clause(representative -> List.of(
184 mayExistingHelper.call(parent, child),
185 strongComponents.call(parent, representative),
186 not(strongComponents.call(child, representative))
187 )));
188
189 storeBuilder.with(PartialRelationTranslator.of(linkType)
190 .may(may)
191 .must(Query.of(name + "#must", (builder, parent, child) -> builder.clause(
192 new MustContainmentLinkView(containsStorage, linkType).call(parent, child)
193 )))
194 .roundingMode(RoundingMode.PREFER_FALSE)
195 .refiner(ContainmentLinkRefiner.of(linkType, containsStorage, info.sourceType(), info.targetType()))
196 .initializer(new RefinementBasedInitializer<>(linkType))
197 .decision(Rule.of(linkType.name(), (builder, source, target) -> builder
198 .clause(
199 may(linkType.call(source, target)),
200 not(candidateMust(linkType.call(source, target))),
201 not(MultiObjectTranslator.MULTI_VIEW.call(source))
202 )
203 .action(focusedTarget -> List.of(
204 focus(target, focusedTarget),
205 add(linkType, source, focusedTarget)
206 )))));
207 }
208
209 private void translateInvalidMultiplicity(ModelStoreBuilder storeBuilder, PartialRelation linkType,
210 ContainmentInfo info) {
211 storeBuilder.with(new InvalidMultiplicityErrorTranslator(info.sourceType(), linkType, false,
212 info.multiplicity()));
213 }
214
215 private void translateContains(ModelStoreBuilder storeBuilder) {
216 var name = CONTAINS_SYMBOL.name();
217 var mustName = DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL);
218
219 storeBuilder.with(PartialRelationTranslator.of(CONTAINS_SYMBOL)
220 .query(Query.of(name, (builder, parent, child) -> {
221 for (var linkType : containmentInfoMap.keySet()) {
222 builder.clause(linkType.call(parent, child));
223 }
224 }))
225 .must(Query.of(mustName, (builder, parent, child) -> builder.clause(
226 new MustContainsView(containsStorage).call(parent, child)
227 ))));
228 }
229
230 private void translateInvalidContainer(ModelStoreBuilder storeBuilder) {
231 storeBuilder.with(new InvalidMultiplicityErrorTranslator(CONTAINED_SYMBOL, CONTAINS_SYMBOL, true,
232 ConstrainedMultiplicity.of(CardinalityIntervals.ONE, INVALID_CONTAINER)));
233 }
234
235 private void translateFocusNotContained(ModelStoreBuilder storeBuilder) {
236 var dseBuilderOption = storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class);
237 if (dseBuilderOption.isEmpty()) {
238 return;
239 }
240 var dseBuilder = dseBuilderOption.get();
241 dseBuilder.transformation(Rule.of("NOT_CONTAINED", (builder, multi) -> builder
242 .clause(
243 MultiObjectTranslator.MULTI_VIEW.call(multi),
244 not(may(CONTAINED_SYMBOL.call(multi)))
245 )
246 .clause((container) -> List.of(
247 MultiObjectTranslator.MULTI_VIEW.call(multi),
248 must(CONTAINS_SYMBOL.call(container, multi)),
249 not(MultiObjectTranslator.MULTI_VIEW.call(container))
250 ))
251 .action(
252 focus(multi, Variable.of())
253 )));
254 }
255}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java
new file mode 100644
index 00000000..e3457fa7
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java
@@ -0,0 +1,24 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
11
12public record ContainmentInfo(PartialRelation sourceType, Multiplicity multiplicity,
13 PartialRelation targetType) {
14 public ContainmentInfo {
15 if (sourceType.arity() != 1) {
16 throw new TranslationException(sourceType, "Expected source type %s to be of arity 1, got %d instead"
17 .formatted(sourceType, sourceType.arity()));
18 }
19 if (targetType.arity() != 1) {
20 throw new TranslationException(targetType, "Expected target type %s to be of arity 1, got %d instead"
21 .formatted(targetType, targetType.arity()));
22 }
23 }
24}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java
new file mode 100644
index 00000000..497ed98f
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java
@@ -0,0 +1,128 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.representation.PartialSymbol;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.ArrayList;
19import java.util.Set;
20
21class ContainmentLinkRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> {
22 private final Factory factory;
23 private final Interpretation<InferredContainment> interpretation;
24 private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner;
25 private final PartialInterpretationRefiner<TruthValue, Boolean> targetRefiner;
26
27 public ContainmentLinkRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
28 Factory factory) {
29 super(adapter, partialSymbol);
30 this.factory = factory;
31 interpretation = adapter.getModel().getInterpretation(factory.symbol);
32 sourceRefiner = adapter.getRefiner(factory.sourceType);
33 targetRefiner = adapter.getRefiner(factory.targetType);
34 }
35
36 @Override
37 public boolean merge(Tuple key, TruthValue value) {
38 var oldValue = interpretation.get(key);
39 var newValue = mergeLink(oldValue, value);
40 if (oldValue != newValue) {
41 interpretation.put(key, newValue);
42 }
43 if (value.must()) {
44 return sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) &&
45 targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE);
46 }
47 return true;
48 }
49
50 public InferredContainment mergeLink(InferredContainment oldValue, TruthValue toMerge) {
51 return switch (toMerge) {
52 case UNKNOWN -> oldValue;
53 case TRUE -> mustLink(oldValue);
54 case FALSE -> forbidLink(oldValue);
55 case ERROR -> errorLink(oldValue);
56 };
57 }
58
59 public InferredContainment mustLink(InferredContainment oldValue) {
60 var mustLinks = oldValue.mustLinks();
61 if (oldValue.contains().may() && mustLinks.isEmpty() && oldValue.forbiddenLinks().isEmpty()) {
62 return factory.trueLink;
63 }
64 if (mustLinks.contains(factory.linkType)) {
65 return oldValue;
66 }
67 return new InferredContainment(oldValue.contains().merge(TruthValue.TRUE),
68 addToSet(mustLinks, factory.linkType), oldValue.forbiddenLinks());
69 }
70
71 public InferredContainment forbidLink(InferredContainment oldValue) {
72 var forbiddenLinks = oldValue.forbiddenLinks();
73 if (oldValue.contains() == TruthValue.UNKNOWN && oldValue.mustLinks().isEmpty() && forbiddenLinks.isEmpty()) {
74 return factory.falseLinkUnknownContains;
75 }
76 if (forbiddenLinks.contains(factory.linkType)) {
77 return oldValue;
78 }
79 return new InferredContainment(oldValue.contains(), oldValue.mustLinks(),
80 addToSet(forbiddenLinks, factory.linkType));
81 }
82
83 public InferredContainment errorLink(InferredContainment oldValue) {
84 return new InferredContainment(TruthValue.ERROR, addToSet(oldValue.mustLinks(), factory.linkType),
85 addToSet(oldValue.forbiddenLinks(), factory.linkType));
86 }
87
88 private static Set<PartialRelation> addToSet(Set<PartialRelation> oldSet, PartialRelation linkType) {
89 if (oldSet.isEmpty()) {
90 return Set.of(linkType);
91 }
92 var newElements = new ArrayList<PartialRelation>(oldSet.size() + 1);
93 newElements.addAll(oldSet);
94 newElements.add(linkType);
95 return Set.copyOf(newElements);
96 }
97
98 public static PartialInterpretationRefiner.Factory<TruthValue, Boolean> of(
99 PartialRelation linkType, Symbol<InferredContainment> symbol, PartialRelation sourceType,
100 PartialRelation targetType) {
101 return new Factory(linkType, symbol, sourceType, targetType);
102 }
103
104 private static class Factory implements PartialInterpretationRefiner.Factory<TruthValue, Boolean> {
105 public final PartialRelation linkType;
106 public final Symbol<InferredContainment> symbol;
107 public final PartialRelation targetType;
108 public final PartialRelation sourceType;
109 public final InferredContainment trueLink;
110 public final InferredContainment falseLinkUnknownContains;
111
112 public Factory(PartialRelation linkType, Symbol<InferredContainment> symbol, PartialRelation sourceType,
113 PartialRelation targetType) {
114 this.linkType = linkType;
115 this.symbol = symbol;
116 this.sourceType = sourceType;
117 this.targetType = targetType;
118 trueLink = new InferredContainment(TruthValue.TRUE, Set.of(linkType), Set.of());
119 falseLinkUnknownContains = new InferredContainment(TruthValue.FALSE, Set.of(), Set.of(linkType));
120 }
121
122 @Override
123 public PartialInterpretationRefiner<TruthValue, Boolean> create(
124 ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol) {
125 return new ContainmentLinkRefiner(adapter, partialSymbol, this);
126 }
127 }
128}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java
new file mode 100644
index 00000000..cf0e2971
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class ForbiddenContainmentLinkView extends InferredContainmentLinkView {
13 public ForbiddenContainmentLinkView(Symbol<InferredContainment> symbol, PartialRelation link) {
14 super(symbol, "must", link);
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredContainment value) {
19 return value.forbiddenLinks().contains(link);
20 }
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java
new file mode 100644
index 00000000..efe674f1
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class ForbiddenContainsView extends TuplePreservingView<InferredContainment> {
13 public ForbiddenContainsView(Symbol<InferredContainment> symbol) {
14 super(symbol, "contains#forbidden");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredContainment value) {
19 return !value.contains().may();
20 }
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java
new file mode 100644
index 00000000..90802864
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.Set;
12
13record InferredContainment(TruthValue contains, Set<PartialRelation> mustLinks,
14 Set<PartialRelation> forbiddenLinks) {
15 public static final InferredContainment UNKNOWN = new InferredContainment(
16 TruthValue.UNKNOWN, Set.of(), Set.of());
17
18 public InferredContainment(TruthValue contains, Set<PartialRelation> mustLinks,
19 Set<PartialRelation> forbiddenLinks) {
20 this.contains = adjustContains(contains, mustLinks, forbiddenLinks);
21 this.mustLinks = mustLinks;
22 this.forbiddenLinks = forbiddenLinks;
23 }
24
25 private static TruthValue adjustContains(TruthValue contains, Set<PartialRelation> mustLinks,
26 Set<PartialRelation> forbiddenLinks) {
27 var result = contains;
28 if (!mustLinks.isEmpty()) {
29 result = result.merge(TruthValue.TRUE);
30 }
31 boolean hasErrorLink = mustLinks.stream().anyMatch(forbiddenLinks::contains);
32 if (mustLinks.size() >= 2 || hasErrorLink) {
33 result = result.merge(TruthValue.ERROR);
34 }
35 return result;
36 }
37}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java
new file mode 100644
index 00000000..d187ad91
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java
@@ -0,0 +1,35 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.Symbol;
11
12import java.util.Objects;
13
14abstract class InferredContainmentLinkView extends TuplePreservingView<InferredContainment> {
15 protected final PartialRelation link;
16
17 protected InferredContainmentLinkView(Symbol<InferredContainment> symbol, String name, PartialRelation link) {
18 super(symbol, link.name() + "#" + name);
19 this.link = link;
20 }
21
22 @Override
23 public boolean equals(Object o) {
24 if (this == o) return true;
25 if (o == null || getClass() != o.getClass()) return false;
26 if (!super.equals(o)) return false;
27 InferredContainmentLinkView that = (InferredContainmentLinkView) o;
28 return Objects.equals(link, that.link);
29 }
30
31 @Override
32 public int hashCode() {
33 return Objects.hash(super.hashCode(), link);
34 }
35}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java
new file mode 100644
index 00000000..474942d6
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class MustContainmentLinkView extends InferredContainmentLinkView {
13 public MustContainmentLinkView(Symbol<InferredContainment> symbol, PartialRelation link) {
14 super(symbol, "must", link);
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredContainment value) {
19 return value.mustLinks().contains(link);
20 }
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java
new file mode 100644
index 00000000..6bc62a59
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class MustContainsView extends TuplePreservingView<InferredContainment> {
13 public MustContainsView(Symbol<InferredContainment> symbol) {
14 super(symbol, "contains#must");
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredContainment value) {
19 return value.contains().must();
20 }
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java
new file mode 100644
index 00000000..c4a2f2b3
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.dnf.RelationalQuery;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
16import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
17
18import java.util.ArrayList;
19import java.util.List;
20
21import static tools.refinery.store.query.literal.Literals.check;
22import static tools.refinery.store.query.term.int_.IntTerms.constant;
23import static tools.refinery.store.query.term.int_.IntTerms.less;
24import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
25
26class CrossReferenceUtils {
27 private CrossReferenceUtils() {
28 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
29 }
30
31 public static RelationalQuery createMayHelper(PartialRelation linkType, PartialRelation type,
32 Multiplicity multiplicity, boolean inverse) {
33 String name;
34 NodeVariable variable;
35 List<Variable> arguments;
36 if (inverse) {
37 name = "Target";
38 variable = Variable.of("target");
39 arguments = List.of(Variable.of("source"), variable);
40 } else {
41 name = "Source";
42 variable = Variable.of("source");
43 arguments = List.of(variable, Variable.of("target"));
44 }
45 var builder = Query.builder(linkType.name() + "#mayNew" + name);
46 builder.parameter(variable);
47 var literals = new ArrayList<Literal>();
48 literals.add(may(type.call(variable)));
49 if (multiplicity.multiplicity().upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) {
50 var existingLinks = Variable.of("existingLinks", Integer.class);
51 literals.add(new CountLowerBoundLiteral(existingLinks, linkType, arguments));
52 literals.add(check(less(existingLinks, constant(finiteUpperCardinality.finiteUpperBound()))));
53 }
54 builder.clause(literals);
55 return builder.build();
56 }
57}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java
new file mode 100644
index 00000000..d8c6a5ea
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java
@@ -0,0 +1,16 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity,
12 PartialRelation targetType, Multiplicity targetMultiplicity) {
13 public boolean isConstrained() {
14 return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained();
15 }
16}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java
new file mode 100644
index 00000000..0700f9f7
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java
@@ -0,0 +1,46 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.tuple.Tuple;
16
17class DirectedCrossReferenceRefiner extends ConcreteSymbolRefiner<TruthValue, Boolean> {
18 private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner;
19 private final PartialInterpretationRefiner<TruthValue, Boolean> targetRefiner;
20
21 public DirectedCrossReferenceRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
22 Symbol<TruthValue> concreteSymbol, PartialRelation sourceType,
23 PartialRelation targetType) {
24 super(adapter, partialSymbol, concreteSymbol);
25 sourceRefiner = adapter.getRefiner(sourceType);
26 targetRefiner = adapter.getRefiner(targetType);
27 }
28
29 @Override
30 public boolean merge(Tuple key, TruthValue value) {
31 if (!super.merge(key, value)) {
32 return false;
33 }
34 if (value.must()) {
35 return sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) &&
36 targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE);
37 }
38 return true;
39 }
40
41 public static Factory<TruthValue, Boolean> of(Symbol<TruthValue> concreteSymbol, PartialRelation sourceType,
42 PartialRelation targetType) {
43 return (adapter, partialSymbol) -> new DirectedCrossReferenceRefiner(adapter, partialSymbol, concreteSymbol,
44 sourceType, targetType);
45 }
46}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java
new file mode 100644
index 00000000..9028337c
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java
@@ -0,0 +1,94 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.dnf.RelationalQuery;
13import tools.refinery.store.query.view.ForbiddenView;
14import tools.refinery.store.reasoning.lifting.DnfLifter;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
21import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24
25import static tools.refinery.store.query.literal.Literals.not;
26import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
27import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
28import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
29
30public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration {
31 private final PartialRelation linkType;
32 private final DirectedCrossReferenceInfo info;
33 private final Symbol<TruthValue> symbol;
34
35 public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) {
36 this.linkType = linkType;
37 this.info = info;
38 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN);
39 }
40
41 @Override
42 public void apply(ModelStoreBuilder storeBuilder) {
43 var name = linkType.name();
44 var sourceType = info.sourceType();
45 var targetType = info.targetType();
46 var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false);
47 var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true);
48 var forbiddenView = new ForbiddenView(symbol);
49 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL);
50
51 storeBuilder.with(PartialRelationTranslator.of(linkType)
52 .symbol(symbol)
53 .may(Query.of(mayName, (builder, source, target) -> {
54 builder.clause(
55 mayNewSource.call(source),
56 mayNewTarget.call(target),
57 not(forbiddenView.call(source, target))
58 );
59 if (info.isConstrained()) {
60 // Violation of monotonicity:
61 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the
62 // corresponding error pattern will already mark the node as invalid.
63 builder.clause(
64 must(linkType.call(source, target)),
65 not(forbiddenView.call(source, target)),
66 may(sourceType.call(source)),
67 may(targetType.call(target))
68 );
69 }
70 }))
71 .refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType))
72 .initializer(new RefinementBasedInitializer<>(linkType))
73 .decision(Rule.of(linkType.name(), (builder, source, target) -> builder
74 .clause(
75 may(linkType.call(source, target)),
76 not(candidateMust(linkType.call(source, target))),
77 not(MULTI_VIEW.call(source)),
78 not(MULTI_VIEW.call(target))
79 )
80 .action(
81 add(linkType, source, target)
82 ))));
83
84 storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false,
85 info.sourceMultiplicity()));
86
87 storeBuilder.with(new InvalidMultiplicityErrorTranslator(targetType, linkType, true,
88 info.targetMultiplicity()));
89 }
90
91 private RelationalQuery createMayHelper(PartialRelation type, Multiplicity multiplicity, boolean inverse) {
92 return CrossReferenceUtils.createMayHelper(linkType, type, multiplicity, inverse);
93 }
94}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java
new file mode 100644
index 00000000..fe99bc54
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity) {
12 public boolean isConstrained() {
13 return multiplicity.isConstrained();
14 }
15}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java
new file mode 100644
index 00000000..43c1462b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.tuple.Tuple;
16
17class UndirectedCrossReferenceRefiner extends ConcreteSymbolRefiner<TruthValue, Boolean> {
18 private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner;
19
20 public UndirectedCrossReferenceRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
21 Symbol<TruthValue> concreteSymbol, PartialRelation sourceType) {
22 super(adapter, partialSymbol, concreteSymbol);
23 sourceRefiner = adapter.getRefiner(sourceType);
24 }
25
26 @Override
27 public boolean merge(Tuple key, TruthValue value) {
28 int source = key.get(0);
29 int target = key.get(1);
30 if (!super.merge(key, value) || !super.merge(Tuple.of(target, source), value)) {
31 return false;
32 }
33 if (value.must()) {
34 return sourceRefiner.merge(Tuple.of(source), TruthValue.TRUE) &&
35 sourceRefiner.merge(Tuple.of(target), TruthValue.TRUE);
36 }
37 return true;
38 }
39
40 public static Factory<TruthValue, Boolean> of(Symbol<TruthValue> concreteSymbol, PartialRelation sourceType) {
41 return (adapter, partialSymbol) -> new UndirectedCrossReferenceRefiner(adapter, partialSymbol, concreteSymbol,
42 sourceType);
43 }
44}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java
new file mode 100644
index 00000000..c554e2a4
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java
@@ -0,0 +1,83 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.view.ForbiddenView;
13import tools.refinery.store.reasoning.lifting.DnfLifter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.Modality;
16import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
20import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue;
22
23import static tools.refinery.store.query.literal.Literals.not;
24import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
26import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
27
28public class UndirectedCrossReferenceTranslator implements ModelStoreConfiguration {
29 private final PartialRelation linkType;
30 private final UndirectedCrossReferenceInfo info;
31 private final Symbol<TruthValue> symbol;
32
33 public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) {
34 this.linkType = linkType;
35 this.info = info;
36 symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN);
37 }
38
39 @Override
40 public void apply(ModelStoreBuilder storeBuilder) {
41 var name = linkType.name();
42 var type = info.type();
43 var forbiddenView = new ForbiddenView(symbol);
44 var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL);
45
46 var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false);
47
48 storeBuilder.with(PartialRelationTranslator.of(linkType)
49 .symbol(symbol)
50 .may(Query.of(mayName, (builder, source, target) -> {
51 builder.clause(
52 mayNewSource.call(source),
53 mayNewSource.call(target),
54 not(forbiddenView.call(source, target))
55 );
56 if (info.isConstrained()) {
57 // Violation of monotonicity:
58 // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the
59 // corresponding error pattern will already mark the node as invalid.
60 builder.clause(
61 must(linkType.call(source, target)),
62 not(forbiddenView.call(source, target)),
63 may(type.call(source)),
64 may(type.call(target))
65 );
66 }
67 }))
68 .refiner(UndirectedCrossReferenceRefiner.of(symbol, type))
69 .initializer(new RefinementBasedInitializer<>(linkType))
70 .decision(Rule.of(linkType.name(), (builder, source, target) -> builder
71 .clause(
72 may(linkType.call(source, target)),
73 not(candidateMust(linkType.call(source, target))),
74 not(MULTI_VIEW.call(source)),
75 not(MULTI_VIEW.call(target))
76 )
77 .action(
78 add(linkType, source, target)
79 ))));
80
81 storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity()));
82 }
83}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java
new file mode 100644
index 00000000..a21da3d4
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java
@@ -0,0 +1,33 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder;
12
13import java.util.Collection;
14
15public class ContainedTypeHierarchyBuilder extends TypeHierarchyBuilder {
16 ContainedTypeHierarchyBuilder() {
17 }
18
19 boolean isInvalidType(PartialRelation type) {
20 return !typeInfoMap.containsKey(type);
21 }
22
23 void setContainedTypes(Collection<PartialRelation> containedTypes) {
24 for (var containedType : containedTypes) {
25 var currentInfo = typeInfoMap.get(containedType);
26 if (currentInfo == null) {
27 throw new TranslationException(containedType, "Invalid contained type: " + containedType);
28 }
29 var newInfo = currentInfo.addSupertype(ContainmentHierarchyTranslator.CONTAINED_SYMBOL);
30 typeInfoMap.put(containedType, newInfo);
31 }
32 }
33}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java
new file mode 100644
index 00000000..72b836ff
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.containment.ContainmentInfo;
10import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo;
11import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo;
12import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy;
13
14import java.util.Map;
15
16public record Metamodel(TypeHierarchy typeHierarchy, Map<PartialRelation, ContainmentInfo> containmentHierarchy,
17 Map<PartialRelation, DirectedCrossReferenceInfo> directedCrossReferences,
18 Map<PartialRelation, UndirectedCrossReferenceInfo> undirectedCrossReferences,
19 Map<PartialRelation, PartialRelation> oppositeReferences) {
20 public static MetamodelBuilder builder() {
21 return new MetamodelBuilder();
22 }
23}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
new file mode 100644
index 00000000..ad0288ed
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java
@@ -0,0 +1,225 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.containment.ContainmentInfo;
12import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo;
13import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo;
14import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
15import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
16import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo;
17
18import java.util.*;
19
20public class MetamodelBuilder {
21 private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder();
22 private final Map<PartialRelation, ReferenceInfo> referenceInfoMap = new LinkedHashMap<>();
23 private final Set<PartialRelation> containedTypes = new HashSet<>();
24 private final Map<PartialRelation, ContainmentInfo> containmentHierarchy = new LinkedHashMap<>();
25 private final Map<PartialRelation, DirectedCrossReferenceInfo> directedCrossReferences = new LinkedHashMap<>();
26 private final Map<PartialRelation, UndirectedCrossReferenceInfo> undirectedCrossReferences = new LinkedHashMap<>();
27 private final Map<PartialRelation, PartialRelation> oppositeReferences = new LinkedHashMap<>();
28
29 MetamodelBuilder() {
30 typeHierarchyBuilder.type(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, true);
31 }
32
33 public MetamodelBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) {
34 typeHierarchyBuilder.type(partialRelation, typeInfo);
35 return this;
36
37 }
38
39 public MetamodelBuilder type(PartialRelation partialRelation, boolean abstractType,
40 PartialRelation... supertypes) {
41 typeHierarchyBuilder.type(partialRelation, abstractType, supertypes);
42 return this;
43 }
44
45 public MetamodelBuilder type(PartialRelation partialRelation, boolean abstractType,
46 Collection<PartialRelation> supertypes) {
47 typeHierarchyBuilder.type(partialRelation, abstractType, supertypes);
48 return this;
49 }
50
51 public MetamodelBuilder type(PartialRelation partialRelation, PartialRelation... supertypes) {
52 typeHierarchyBuilder.type(partialRelation, supertypes);
53 return this;
54 }
55
56 public MetamodelBuilder type(PartialRelation partialRelation, Collection<PartialRelation> supertypes) {
57 typeHierarchyBuilder.type(partialRelation, supertypes);
58 return this;
59 }
60
61 public MetamodelBuilder types(Collection<Map.Entry<PartialRelation, TypeInfo>> entries) {
62 typeHierarchyBuilder.types(entries);
63 return this;
64 }
65
66 public MetamodelBuilder types(Map<PartialRelation, TypeInfo> map) {
67 typeHierarchyBuilder.types(map);
68 return this;
69 }
70
71 public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) {
72 if (linkType.arity() != 2) {
73 throw new TranslationException(linkType,
74 "Only references of arity 2 are supported, got %s with %d instead".formatted(
75 linkType, linkType.arity()));
76 }
77 var putResult = referenceInfoMap.put(linkType, info);
78 if (putResult != null && !putResult.equals(info)) {
79 throw new TranslationException(linkType, "Duplicate reference info for partial relation: " + linkType);
80 }
81 return this;
82 }
83
84 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
85 Multiplicity multiplicity, PartialRelation targetType,
86 PartialRelation opposite) {
87 return reference(linkType, new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite));
88 }
89
90 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity,
91 PartialRelation targetType, PartialRelation opposite) {
92 return reference(linkType, sourceType, false, multiplicity, targetType, opposite);
93 }
94
95 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType,
96 boolean containment, PartialRelation targetType, PartialRelation opposite) {
97 return reference(linkType, sourceType, containment, UnconstrainedMultiplicity.INSTANCE, targetType, opposite);
98 }
99
100 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, PartialRelation targetType,
101 PartialRelation opposite) {
102 return reference(linkType, sourceType, UnconstrainedMultiplicity.INSTANCE, targetType, opposite);
103 }
104
105 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
106 Multiplicity multiplicity, PartialRelation targetType) {
107 return reference(linkType, sourceType, containment, multiplicity, targetType, null);
108 }
109
110 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity,
111 PartialRelation targetType) {
112 return reference(linkType, sourceType, multiplicity, targetType, null);
113 }
114
115 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment,
116 PartialRelation targetType) {
117 return reference(linkType, sourceType, containment, targetType, null);
118 }
119
120 public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType,
121 PartialRelation targetType) {
122 return reference(linkType, sourceType, targetType, null);
123 }
124
125 public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) {
126 for (var entry : entries) {
127 reference(entry.getKey(), entry.getValue());
128 }
129 return this;
130 }
131
132 public MetamodelBuilder references(Map<PartialRelation, ReferenceInfo> map) {
133 return references(map.entrySet());
134 }
135
136 public Metamodel build() {
137 for (var entry : referenceInfoMap.entrySet()) {
138 var linkType = entry.getKey();
139 var info = entry.getValue();
140 processReferenceInfo(linkType, info);
141 }
142 typeHierarchyBuilder.setContainedTypes(containedTypes);
143 var typeHierarchy = typeHierarchyBuilder.build();
144 return new Metamodel(typeHierarchy, Collections.unmodifiableMap(containmentHierarchy),
145 Collections.unmodifiableMap(directedCrossReferences),
146 Collections.unmodifiableMap(undirectedCrossReferences),
147 Collections.unmodifiableMap(oppositeReferences));
148 }
149
150 private void processReferenceInfo(PartialRelation linkType, ReferenceInfo info) {
151 if (oppositeReferences.containsKey(linkType) || containmentHierarchy.containsKey(linkType)) {
152 // We already processed this reference while processing its opposite.
153 return;
154 }
155 var sourceType = info.sourceType();
156 var targetType = info.targetType();
157 if (typeHierarchyBuilder.isInvalidType(sourceType)) {
158 throw new TranslationException(linkType, "Source type %s of %s is not in type hierarchy"
159 .formatted(sourceType, linkType));
160 }
161 if (typeHierarchyBuilder.isInvalidType(targetType)) {
162 throw new TranslationException(linkType, "Target type %s of %s is not in type hierarchy"
163 .formatted(targetType, linkType));
164 }
165 var opposite = info.opposite();
166 Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE;
167 if (opposite != null) {
168 var oppositeInfo = referenceInfoMap.get(opposite);
169 validateOpposite(linkType, info, opposite, oppositeInfo);
170 targetMultiplicity = oppositeInfo.multiplicity();
171 if (oppositeInfo.containment()) {
172 // Skip processing this reference and process it once we encounter its containment opposite.
173 return;
174 }
175 if (opposite.equals(linkType)) {
176 if (!sourceType.equals(targetType)) {
177 throw new TranslationException(linkType,
178 "Target %s of undirected reference %s differs from source %s".formatted(
179 targetType, linkType, sourceType));
180 }
181 undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType,
182 info.multiplicity()));
183 return;
184 }
185 oppositeReferences.put(opposite, linkType);
186 }
187 if (info.containment()) {
188 if (!UnconstrainedMultiplicity.INSTANCE.equals(targetMultiplicity)) {
189 throw new TranslationException(opposite, "Invalid opposite %s with multiplicity %s of containment %s"
190 .formatted(opposite, targetMultiplicity, linkType));
191 }
192 containedTypes.add(targetType);
193 containmentHierarchy.put(linkType, new ContainmentInfo(sourceType, info.multiplicity(), targetType));
194 return;
195 }
196 directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(),
197 targetType, targetMultiplicity));
198 }
199
200 private static void validateOpposite(PartialRelation linkType, ReferenceInfo info, PartialRelation opposite,
201 ReferenceInfo oppositeInfo) {
202 var sourceType = info.sourceType();
203 var targetType = info.targetType();
204 if (oppositeInfo == null) {
205 throw new TranslationException(linkType, "Opposite %s of %s is not defined"
206 .formatted(opposite, linkType));
207 }
208 if (!linkType.equals(oppositeInfo.opposite())) {
209 throw new TranslationException(opposite, "Expected %s to have opposite %s, got %s instead"
210 .formatted(opposite, linkType, oppositeInfo.opposite()));
211 }
212 if (!targetType.equals(oppositeInfo.sourceType())) {
213 throw new TranslationException(linkType, "Expected %s to have source type %s, got %s instead"
214 .formatted(opposite, targetType, oppositeInfo.sourceType()));
215 }
216 if (!sourceType.equals(oppositeInfo.targetType())) {
217 throw new TranslationException(linkType, "Expected %s to have target type %s, got %s instead"
218 .formatted(opposite, sourceType, oppositeInfo.targetType()));
219 }
220 if (oppositeInfo.containment() && info.containment()) {
221 throw new TranslationException(opposite, "Opposite %s of containment %s cannot be containment"
222 .formatted(opposite, linkType));
223 }
224 }
225}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java
new file mode 100644
index 00000000..5afa58f2
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceTranslator;
12import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceTranslator;
13import tools.refinery.store.reasoning.translator.opposite.OppositeRelationTranslator;
14import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
15
16public class MetamodelTranslator implements ModelStoreConfiguration {
17 private final Metamodel metamodel;
18
19 public MetamodelTranslator(Metamodel metamodel) {
20 this.metamodel = metamodel;
21 }
22
23 @Override
24 public void apply(ModelStoreBuilder storeBuilder) {
25 storeBuilder.with(new TypeHierarchyTranslator(metamodel.typeHierarchy()));
26 storeBuilder.with(new ContainmentHierarchyTranslator(metamodel.containmentHierarchy()));
27 for (var entry : metamodel.directedCrossReferences().entrySet()) {
28 storeBuilder.with(new DirectedCrossReferenceTranslator(entry.getKey(), entry.getValue()));
29 }
30 for (var entry : metamodel.undirectedCrossReferences().entrySet()) {
31 storeBuilder.with(new UndirectedCrossReferenceTranslator(entry.getKey(), entry.getValue()));
32 }
33 for (var entry : metamodel.oppositeReferences().entrySet()) {
34 storeBuilder.with(new OppositeRelationTranslator(entry.getKey(), entry.getValue()));
35 }
36 }
37}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java
new file mode 100644
index 00000000..9a6b4012
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity,
12 PartialRelation targetType, PartialRelation opposite) {
13}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java
new file mode 100644
index 00000000..d8db4ec4
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java
@@ -0,0 +1,64 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.representation.cardinality.CardinalityInterval;
15import tools.refinery.store.representation.cardinality.CardinalityIntervals;
16import tools.refinery.store.tuple.Tuple;
17
18public class EqualsRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> {
19 private final Interpretation<CardinalityInterval> countInterpretation;
20
21 private EqualsRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
22 Symbol<CardinalityInterval> countSymbol) {
23 super(adapter, partialSymbol);
24 countInterpretation = adapter.getModel().getInterpretation(countSymbol);
25 }
26
27 @Override
28 public boolean merge(Tuple key, TruthValue value) {
29 if (value == TruthValue.UNKNOWN) {
30 return true;
31 }
32 if (value == TruthValue.ERROR) {
33 return false;
34 }
35 int left = key.get(0);
36 int right = key.get(1);
37 boolean isDiagonal = left == right;
38 if (isDiagonal && value == TruthValue.FALSE) {
39 return false;
40 }
41 if (!isDiagonal) {
42 return !value.may();
43 }
44 if (value != TruthValue.TRUE) {
45 throw new IllegalArgumentException("Unknown TruthValue: " + value);
46 }
47 // {@code isDiagonal} is true, so this could be {@code left} or {@code right}.
48 var unaryKey = Tuple.of(left);
49 var currentCount = countInterpretation.get(unaryKey);
50 if (currentCount == null) {
51 return false;
52 }
53 var newCount = currentCount.meet(CardinalityIntervals.LONE);
54 if (newCount.isEmpty()) {
55 return false;
56 }
57 countInterpretation.put(unaryKey, newCount);
58 return true;
59 }
60
61 public static Factory<TruthValue, Boolean> of(Symbol<CardinalityInterval> countSymbol) {
62 return (adapter, partialSymbol) -> new EqualsRefiner(adapter, partialSymbol, countSymbol);
63 }
64}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java
new file mode 100644
index 00000000..61b9488c
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java
@@ -0,0 +1,85 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.dnf.RelationalQuery;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.CallLiteral;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.literal.Modality;
18import tools.refinery.store.representation.cardinality.UpperCardinalities;
19import tools.refinery.store.representation.cardinality.UpperCardinality;
20
21import java.util.List;
22import java.util.Set;
23
24import static tools.refinery.store.query.literal.Literals.check;
25import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant;
26import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.lessEq;
27
28class EqualsRelationRewriter extends QueryBasedRelationRewriter {
29 private EqualsRelationRewriter(RelationalQuery may, RelationalQuery must) {
30 super(may, must, may, may);
31 }
32
33 @Override
34 public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal,
35 Modality modality, Concreteness concreteness) {
36 if (!(literal instanceof CallLiteral callLiteral)) {
37 return super.rewriteLiteral(positiveVariables, literal, modality, concreteness);
38 }
39 var left = callLiteral.getArguments().get(0).asNodeVariable();
40 var right = callLiteral.getArguments().get(1).asNodeVariable();
41 boolean useMay = modality == Modality.MAY || concreteness == Concreteness.CANDIDATE;
42 return switch (callLiteral.getPolarity()) {
43 case POSITIVE, TRANSITIVE -> {
44 if (useMay) {
45 if (positiveVariables.contains(left) || positiveVariables.contains(right)) {
46 // No need to enumerate arguments if at least one is already bound, since they will be unified.
47 yield List.of(left.isEquivalent(right));
48 } else {
49 yield List.of(
50 left.isEquivalent(right),
51 getMay().call(left, right)
52 );
53 }
54 } else {
55 yield List.of(
56 left.isEquivalent(right),
57 getMust().call(left, right)
58 );
59 }
60 }
61 case NEGATIVE -> {
62 if (useMay) {
63 yield List.of(left.notEquivalent(right));
64 } else {
65 yield super.rewriteLiteral(positiveVariables, literal, modality, concreteness);
66 }
67 }
68 };
69 }
70
71 public static EqualsRelationRewriter of(AnySymbolView upperCardinalityView) {
72 var may = Query.of("equals#may", (builder, p1, p2) -> builder
73 .clause(
74 p1.isEquivalent(p2),
75 upperCardinalityView.call(p1, Variable.of(UpperCardinality.class))
76 ));
77 var must = Query.of("equals#must", (builder, p1, p2) -> builder
78 .clause(UpperCardinality.class, upper -> List.of(
79 p1.isEquivalent(p2),
80 upperCardinalityView.call(p1, upper),
81 check(lessEq(upper, constant(UpperCardinalities.ONE)))
82 )));
83 return new EqualsRelationRewriter(may, must);
84 }
85}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java
new file mode 100644
index 00000000..f134fe92
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java
@@ -0,0 +1,55 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.representation.cardinality.CardinalityInterval;
15import tools.refinery.store.representation.cardinality.CardinalityIntervals;
16import tools.refinery.store.tuple.Tuple;
17
18public class ExistsRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> {
19 private final Interpretation<CardinalityInterval> countInterpretation;
20
21 private ExistsRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
22 Symbol<CardinalityInterval> countSymbol) {
23 super(adapter, partialSymbol);
24 countInterpretation = adapter.getModel().getInterpretation(countSymbol);
25 }
26
27 @Override
28 public boolean merge(Tuple key, TruthValue value) {
29 var currentCount = countInterpretation.get(key);
30 if (currentCount == null) {
31 return false;
32 }
33 CardinalityInterval newCount;
34 switch (value) {
35 case UNKNOWN -> {
36 return true;
37 }
38 case TRUE -> newCount = currentCount.meet(CardinalityIntervals.SOME);
39 case FALSE -> newCount = currentCount.meet(CardinalityIntervals.NONE);
40 case ERROR -> {
41 return false;
42 }
43 default -> throw new IllegalArgumentException("Unknown TruthValue: " + value);
44 }
45 if (newCount.isEmpty()) {
46 return false;
47 }
48 countInterpretation.put(key, newCount);
49 return true;
50 }
51
52 public static Factory<TruthValue, Boolean> of(Symbol<CardinalityInterval> countSymbol) {
53 return (adapter, partialSymbol) -> new ExistsRefiner(adapter, partialSymbol, countSymbol);
54 }
55}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java
new file mode 100644
index 00000000..9873888c
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.view.AbstractFunctionView;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.representation.cardinality.CardinalityInterval;
12
13class LowerCardinalityView extends AbstractFunctionView<CardinalityInterval> {
14 public LowerCardinalityView(Symbol<CardinalityInterval> symbol) {
15 super(symbol, "lower", new Parameter(Integer.class));
16 }
17
18 @Override
19 protected Object forwardMapValue(CardinalityInterval value) {
20 return value.lowerBound();
21 }
22}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java
new file mode 100644
index 00000000..f11ab46b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java
@@ -0,0 +1,131 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
12import tools.refinery.store.reasoning.seed.ModelSeed;
13import tools.refinery.store.reasoning.translator.TranslationException;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.representation.cardinality.CardinalityInterval;
17import tools.refinery.store.representation.cardinality.CardinalityIntervals;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.Arrays;
21import java.util.HashMap;
22import java.util.function.Function;
23
24class MultiObjectInitializer implements PartialModelInitializer {
25 private final Symbol<CardinalityInterval> countSymbol;
26
27 public MultiObjectInitializer(Symbol<CardinalityInterval> countSymbol) {
28 this.countSymbol = countSymbol;
29 }
30
31 @Override
32 public void initialize(Model model, ModelSeed modelSeed) {
33 var intervals = initializeIntervals(modelSeed);
34 initializeExists(intervals, modelSeed);
35 initializeEquals(intervals, modelSeed);
36 var countInterpretation = model.getInterpretation(countSymbol);
37 var uniqueTable = new HashMap<CardinalityInterval, CardinalityInterval>();
38 for (int i = 0; i < intervals.length; i++) {
39 var interval = intervals[i];
40 if (interval.isEmpty()) {
41 throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
42 "Inconsistent existence or equality for node " + i);
43 }
44 var uniqueInterval = uniqueTable.computeIfAbsent(intervals[i], Function.identity());
45 countInterpretation.put(Tuple.of(i), uniqueInterval);
46 }
47 }
48
49 @NotNull
50 private CardinalityInterval[] initializeIntervals(ModelSeed modelSeed) {
51 var intervals = new CardinalityInterval[modelSeed.getNodeCount()];
52 if (modelSeed.containsSeed(MultiObjectTranslator.COUNT_SYMBOL)) {
53 Arrays.fill(intervals, CardinalityIntervals.ONE);
54 var cursor = modelSeed.getCursor(MultiObjectTranslator.COUNT_SYMBOL, CardinalityIntervals.ONE);
55 while (cursor.move()) {
56 int i = cursor.getKey().get(0);
57 checkNodeId(intervals, i);
58 intervals[i] = cursor.getValue();
59 }
60 } else {
61 Arrays.fill(intervals, CardinalityIntervals.SET);
62 if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL) ||
63 !modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) {
64 throw new TranslationException(MultiObjectTranslator.COUNT_SYMBOL,
65 "Seed for %s and %s is required if there is no seed for %s".formatted(
66 ReasoningAdapter.EXISTS_SYMBOL, ReasoningAdapter.EQUALS_SYMBOL,
67 MultiObjectTranslator.COUNT_SYMBOL));
68 }
69 }
70 return intervals;
71 }
72
73 private void initializeExists(CardinalityInterval[] intervals, ModelSeed modelSeed) {
74 if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL)) {
75 return;
76 }
77 var cursor = modelSeed.getCursor(ReasoningAdapter.EXISTS_SYMBOL, TruthValue.UNKNOWN);
78 while (cursor.move()) {
79 int i = cursor.getKey().get(0);
80 checkNodeId(intervals, i);
81 switch (cursor.getValue()) {
82 case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.SOME);
83 case FALSE -> intervals[i] = intervals[i].meet(CardinalityIntervals.NONE);
84 case ERROR -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
85 "Inconsistent existence for node " + i);
86 default -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL,
87 "Invalid existence truth value %s for node %d".formatted(cursor.getValue(), i));
88 }
89 }
90 }
91
92 private void initializeEquals(CardinalityInterval[] intervals, ModelSeed modelSeed) {
93 if (!modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) {
94 return;
95 }
96 var seed = modelSeed.getSeed(ReasoningAdapter.EQUALS_SYMBOL);
97 var cursor = seed.getCursor(TruthValue.FALSE, modelSeed.getNodeCount());
98 while (cursor.move()) {
99 var key = cursor.getKey();
100 int i = key.get(0);
101 int otherIndex = key.get(1);
102 if (i != otherIndex) {
103 throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
104 "Off-diagonal equivalence (%d, %d) is not permitted".formatted(i, otherIndex));
105 }
106 checkNodeId(intervals, i);
107 switch (cursor.getValue()) {
108 case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.LONE);
109 case UNKNOWN -> {
110 // Nothing do to, {@code intervals} is initialized with unknown equality.
111 }
112 case ERROR -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
113 "Inconsistent equality for node " + i);
114 default -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL,
115 "Invalid equality truth value %s for node %d".formatted(cursor.getValue(), i));
116 }
117 }
118 for (int i = 0; i < intervals.length; i++) {
119 if (seed.get(Tuple.of(i, i)) == TruthValue.FALSE) {
120 throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Inconsistent equality for node " + i);
121 }
122 }
123 }
124
125 private void checkNodeId(CardinalityInterval[] intervals, int nodeId) {
126 if (nodeId < 0 || nodeId >= intervals.length) {
127 throw new IllegalArgumentException("Expected node id %d to be lower than model size %d"
128 .formatted(nodeId, intervals.length));
129 }
130 }
131}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java
new file mode 100644
index 00000000..e48934d8
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java
@@ -0,0 +1,45 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.refinement.StorageRefiner;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.cardinality.CardinalityInterval;
13import tools.refinery.store.representation.cardinality.CardinalityIntervals;
14import tools.refinery.store.tuple.Tuple;
15
16class MultiObjectStorageRefiner implements StorageRefiner {
17 private final Interpretation<CardinalityInterval> countInterpretation;
18
19 public MultiObjectStorageRefiner(Symbol<CardinalityInterval> countSymbol, Model model) {
20 countInterpretation = model.getInterpretation(countSymbol);
21 }
22
23 @Override
24 public boolean split(int parentNode, int childNode) {
25 var parentKey = Tuple.of(parentNode);
26 var parentCount = countInterpretation.get(parentKey);
27 if (parentCount == null) {
28 return false;
29 }
30 var newParentCount = parentCount.take(1);
31 if (newParentCount.isEmpty()) {
32 return false;
33 }
34 var childKey = Tuple.of(childNode);
35 countInterpretation.put(parentKey, newParentCount);
36 countInterpretation.put(childKey, CardinalityIntervals.ONE);
37 return true;
38 }
39
40 @Override
41 public boolean cleanup(int nodeToDelete) {
42 var previousCount = countInterpretation.put(Tuple.of(nodeToDelete), null);
43 return previousCount.lowerBound() == 0;
44 }
45}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java
new file mode 100644
index 00000000..05704096
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java
@@ -0,0 +1,86 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.dse.transition.objectives.Criteria;
9import tools.refinery.store.dse.transition.objectives.Objectives;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.model.ModelStoreConfiguration;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.term.int_.IntTerms;
15import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
16import tools.refinery.store.query.view.AnySymbolView;
17import tools.refinery.store.reasoning.ReasoningAdapter;
18import tools.refinery.store.reasoning.ReasoningBuilder;
19import tools.refinery.store.reasoning.representation.PartialFunction;
20import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
21import tools.refinery.store.reasoning.translator.RoundingMode;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.cardinality.CardinalityDomain;
24import tools.refinery.store.representation.cardinality.CardinalityInterval;
25import tools.refinery.store.representation.cardinality.UpperCardinalities;
26import tools.refinery.store.representation.cardinality.UpperCardinality;
27
28import java.util.List;
29
30import static tools.refinery.store.query.literal.Literals.check;
31import static tools.refinery.store.query.term.int_.IntTerms.*;
32
33public class MultiObjectTranslator implements ModelStoreConfiguration {
34 public static final Symbol<CardinalityInterval> COUNT_STORAGE = Symbol.of("COUNT", 1, CardinalityInterval.class,
35 null);
36 public static final AnySymbolView LOWER_CARDINALITY_VIEW = new LowerCardinalityView(COUNT_STORAGE);
37 public static final AnySymbolView UPPER_CARDINALITY_VIEW = new UpperCardinalityView(COUNT_STORAGE);
38 public static final AnySymbolView MULTI_VIEW = new MultiView(COUNT_STORAGE);
39 public static final PartialFunction<CardinalityInterval, Integer> COUNT_SYMBOL = new PartialFunction<>("COUNT", 1,
40 CardinalityDomain.INSTANCE);
41
42 @Override
43 public void apply(ModelStoreBuilder storeBuilder) {
44 storeBuilder.symbol(COUNT_STORAGE);
45
46 var aboveLowerBound = Query.of("count#aboveLowerBound", Integer.class, (builder, node, output) -> builder
47 .clause(Integer.class, lowerBound -> List.of(
48 LOWER_CARDINALITY_VIEW.call(node, lowerBound),
49 output.assign(sub(lowerBound, IntTerms.constant(1))),
50 check(greater(output, IntTerms.constant(0)))
51 )));
52 var missingCardinality = Query.of("count#missing", Integer.class, (builder, output) -> builder
53 .clause(
54 output.assign(aboveLowerBound.aggregate(INT_SUM, Variable.of()))
55 ));
56
57 storeBuilder.with(PartialRelationTranslator.of(ReasoningAdapter.EXISTS_SYMBOL)
58 .may(Query.of("exists#may", (builder, p1) -> builder
59 .clause(UpperCardinality.class, upper -> List.of(
60 UPPER_CARDINALITY_VIEW.call(p1, upper),
61 check(UpperCardinalityTerms.greaterEq(upper,
62 UpperCardinalityTerms.constant(UpperCardinalities.ONE)))
63 ))))
64 .must(Query.of("exists#must", (builder, p1) -> builder
65 .clause(Integer.class, lower -> List.of(
66 LOWER_CARDINALITY_VIEW.call(p1, lower),
67 check(greaterEq(lower, constant(1)))
68 ))))
69 .roundingMode(RoundingMode.PREFER_FALSE)
70 .refiner(ExistsRefiner.of(COUNT_STORAGE))
71 .exclude(null)
72 .accept(Criteria.whenNoMatch(aboveLowerBound))
73 .objective(Objectives.value(missingCardinality)));
74
75 storeBuilder.with(PartialRelationTranslator.of(ReasoningAdapter.EQUALS_SYMBOL)
76 .rewriter(EqualsRelationRewriter.of(UPPER_CARDINALITY_VIEW))
77 .refiner(EqualsRefiner.of(COUNT_STORAGE))
78 .exclude(null)
79 .accept(null)
80 .objective(null));
81
82 var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class);
83 reasoningBuilder.initializer(new MultiObjectInitializer(COUNT_STORAGE));
84 reasoningBuilder.storageRefiner(COUNT_STORAGE, MultiObjectStorageRefiner::new);
85 }
86}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java
new file mode 100644
index 00000000..498bcd83
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12import tools.refinery.store.tuple.Tuple;
13
14class MultiView extends TuplePreservingView<CardinalityInterval> {
15 protected MultiView(Symbol<CardinalityInterval> symbol) {
16 super(symbol, "multi");
17 }
18
19 @Override
20 protected boolean doFilter(Tuple key, CardinalityInterval value) {
21 return !CardinalityIntervals.ONE.equals(value);
22 }
23}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java
new file mode 100644
index 00000000..6be6ae1b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java
@@ -0,0 +1,23 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.view.AbstractFunctionView;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.representation.cardinality.CardinalityInterval;
12import tools.refinery.store.representation.cardinality.UpperCardinality;
13
14class UpperCardinalityView extends AbstractFunctionView<CardinalityInterval> {
15 public UpperCardinalityView(Symbol<CardinalityInterval> symbol) {
16 super(symbol, "upper", new Parameter(UpperCardinality.class));
17 }
18
19 @Override
20 protected Object forwardMapValue(CardinalityInterval value) {
21 return value.upperBound();
22 }
23}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java
new file mode 100644
index 00000000..9db9cc96
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval;
13
14public record ConstrainedMultiplicity(NonEmptyCardinalityInterval multiplicity, PartialRelation errorSymbol)
15 implements Multiplicity {
16 public ConstrainedMultiplicity {
17 if (multiplicity.equals(CardinalityIntervals.SET)) {
18 throw new TranslationException(errorSymbol, "Expected a constrained cardinality interval");
19 }
20 if (errorSymbol.arity() != 1) {
21 throw new TranslationException(errorSymbol, "Expected error symbol %s to have arity 1, got %d instead"
22 .formatted(errorSymbol, errorSymbol.arity()));
23 }
24 }
25
26 public static ConstrainedMultiplicity of(CardinalityInterval multiplicity, PartialRelation errorSymbol) {
27 if (!(multiplicity instanceof NonEmptyCardinalityInterval nonEmptyCardinalityInterval)) {
28 throw new TranslationException(errorSymbol, "Inconsistent multiplicity");
29 }
30 return new ConstrainedMultiplicity(nonEmptyCardinalityInterval, errorSymbol);
31 }
32
33 @Override
34 public boolean isConstrained() {
35 return true;
36 }
37}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java
new file mode 100644
index 00000000..ee982f4f
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java
@@ -0,0 +1,139 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.dse.transition.objectives.Objectives;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14import tools.refinery.store.reasoning.lifting.DnfLifter;
15import tools.refinery.store.reasoning.literal.*;
16import tools.refinery.store.reasoning.representation.PartialRelation;
17import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
18import tools.refinery.store.reasoning.translator.TranslationException;
19import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
20import tools.refinery.store.representation.cardinality.UpperCardinalities;
21import tools.refinery.store.representation.cardinality.UpperCardinality;
22
23import java.util.List;
24
25import static tools.refinery.store.query.literal.Literals.check;
26import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
27import static tools.refinery.store.query.term.int_.IntTerms.constant;
28import static tools.refinery.store.query.term.int_.IntTerms.greater;
29import static tools.refinery.store.query.term.int_.IntTerms.sub;
30import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant;
31import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.less;
32import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust;
33import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
34
35public class InvalidMultiplicityErrorTranslator implements ModelStoreConfiguration {
36 private final PartialRelation nodeType;
37 private final PartialRelation linkType;
38 private final boolean inverse;
39 private final Multiplicity multiplicity;
40
41 public InvalidMultiplicityErrorTranslator(PartialRelation nodeType, PartialRelation linkType,
42 boolean inverse, Multiplicity multiplicity) {
43 if (nodeType.arity() != 1) {
44 throw new TranslationException(linkType, "Node type must be of arity 1, got %s with arity %d instead"
45 .formatted(nodeType, nodeType.arity()));
46 }
47 if (linkType.arity() != 2) {
48 throw new TranslationException(linkType, "Link type must be of arity 2, got %s with arity %d instead"
49 .formatted(linkType, linkType.arity()));
50 }
51 this.nodeType = nodeType;
52 this.linkType = linkType;
53 this.inverse = inverse;
54 this.multiplicity = multiplicity;
55 }
56
57 @Override
58 public void apply(ModelStoreBuilder storeBuilder) {
59 if (!(multiplicity instanceof ConstrainedMultiplicity constrainedMultiplicity)) {
60 return;
61 }
62
63 var name = constrainedMultiplicity.errorSymbol().name();
64 var cardinalityInterval = constrainedMultiplicity.multiplicity();
65 var node = Variable.of("node");
66 var other = Variable.of("other");
67 List<Variable> arguments = inverse ? List.of(other, node) : List.of(node, other);
68 var mustBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL))
69 .parameter(node);
70 var candidateMayBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL))
71 .parameter(node);
72 var candidateMustBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL))
73 .parameter(node);
74 var missingOutput = Variable.of("missing", Integer.class);
75 var missingBuilder = Query.builder(name + "#missingMultiplicity").parameter(node).output(missingOutput);
76
77 int lowerBound = cardinalityInterval.lowerBound();
78 if (lowerBound > 0) {
79 var lowerBoundCardinality = UpperCardinalities.atMost(lowerBound);
80 mustBuilder.clause(UpperCardinality.class, existingContents -> List.of(
81 must(nodeType.call(node)),
82 new CountUpperBoundLiteral(existingContents, linkType, arguments),
83 check(less(existingContents, constant(lowerBoundCardinality)))
84 ));
85 candidateMayBuilder.clause(Integer.class, existingContents -> List.of(
86 candidateMust(nodeType.call(node)),
87 new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments),
88 check(IntTerms.less(existingContents, constant(lowerBound)))
89 ));
90 candidateMustBuilder.clause(Integer.class, existingContents -> List.of(
91 candidateMust(nodeType.call(node)),
92 new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments),
93 check(IntTerms.less(existingContents, constant(lowerBound)))
94 ));
95 missingBuilder.clause(Integer.class, existingContents -> List.of(
96 candidateMust(nodeType.call(node)),
97 new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments),
98 missingOutput.assign(sub(constant(lowerBound), existingContents)),
99 check(greater(missingOutput, constant(0)))
100 ));
101 }
102
103 if (cardinalityInterval.upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) {
104 int upperBound = finiteUpperCardinality.finiteUpperBound();
105 mustBuilder.clause(Integer.class, existingContents -> List.of(
106 must(nodeType.call(node)),
107 new CountLowerBoundLiteral(existingContents, linkType, arguments),
108 check(greater(existingContents, constant(upperBound)))
109 ));
110 candidateMayBuilder.clause(Integer.class, existingContents -> List.of(
111 candidateMust(nodeType.call(node)),
112 new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments),
113 check(greater(existingContents, constant(upperBound)))
114 ));
115 candidateMustBuilder.clause(Integer.class, existingContents -> List.of(
116 candidateMust(nodeType.call(node)),
117 new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments),
118 check(greater(existingContents, constant(upperBound)))
119 ));
120 missingBuilder.clause(Integer.class, existingContents -> List.of(
121 candidateMust(nodeType.call(node)),
122 new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments),
123 missingOutput.assign(sub(existingContents, constant(upperBound))),
124 check(greater(missingOutput, constant(0)))
125 ));
126 }
127
128 var objective = Query.of(name + "#objective", Integer.class, (builder, output) -> builder.clause(
129 output.assign(missingBuilder.build().aggregate(INT_SUM, Variable.of()))
130 ));
131
132 storeBuilder.with(PartialRelationTranslator.of(constrainedMultiplicity.errorSymbol())
133 .mayNever()
134 .must(mustBuilder.build())
135 .candidateMay(candidateMayBuilder.build())
136 .candidateMust(candidateMustBuilder.build())
137 .objective(Objectives.value(objective)));
138 }
139}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java
new file mode 100644
index 00000000..d1d6dd1e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.representation.cardinality.CardinalityInterval;
9
10public sealed interface Multiplicity permits ConstrainedMultiplicity, UnconstrainedMultiplicity {
11 CardinalityInterval multiplicity();
12
13 boolean isConstrained();
14}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java
new file mode 100644
index 00000000..2159b88c
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java
@@ -0,0 +1,28 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.representation.cardinality.CardinalityInterval;
9import tools.refinery.store.representation.cardinality.CardinalityIntervals;
10
11// Singleton implementation, because there is only a single complete interval.
12@SuppressWarnings("squid:S6548")
13public final class UnconstrainedMultiplicity implements Multiplicity {
14 public static final UnconstrainedMultiplicity INSTANCE = new UnconstrainedMultiplicity();
15
16 private UnconstrainedMultiplicity() {
17 }
18
19 @Override
20 public CardinalityInterval multiplicity() {
21 return CardinalityIntervals.SET;
22 }
23
24 @Override
25 public boolean isConstrained() {
26 return true;
27 }
28}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java
new file mode 100644
index 00000000..7290ab40
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java
@@ -0,0 +1,77 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8
9import tools.refinery.store.map.AnyVersionedMap;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.reasoning.ReasoningAdapter;
12import tools.refinery.store.reasoning.interpretation.AbstractPartialInterpretation;
13import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialSymbol;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.Set;
19
20class OppositeInterpretation<A, C> extends AbstractPartialInterpretation<A, C> {
21 private final PartialInterpretation<A, C> opposite;
22
23 private OppositeInterpretation(ReasoningAdapter adapter, Concreteness concreteness,
24 PartialSymbol<A, C> partialSymbol, PartialInterpretation<A, C> opposite) {
25 super(adapter, concreteness, partialSymbol);
26 this.opposite = opposite;
27 }
28
29 @Override
30 public A get(Tuple key) {
31 return opposite.get(OppositeUtils.flip(key));
32 }
33
34 @Override
35 public Cursor<Tuple, A> getAll() {
36 return new OppositeCursor<>(opposite.getAll());
37 }
38
39 public static <A1, C1> Factory<A1, C1> of(PartialSymbol<A1, C1> oppositeSymbol) {
40 return (adapter, concreteness, partialSymbol) -> {
41 var opposite = adapter.getPartialInterpretation(concreteness, oppositeSymbol);
42 return new OppositeInterpretation<>(adapter, concreteness, partialSymbol, opposite);
43 };
44 }
45
46 private record OppositeCursor<T>(Cursor<Tuple, T> opposite) implements Cursor<Tuple, T> {
47 @Override
48 public Tuple getKey() {
49 return OppositeUtils.flip(opposite.getKey());
50 }
51
52 @Override
53 public T getValue() {
54 return opposite.getValue();
55 }
56
57 @Override
58 public boolean isTerminated() {
59 return opposite.isTerminated();
60 }
61
62 @Override
63 public boolean move() {
64 return opposite.move();
65 }
66
67 @Override
68 public Set<AnyVersionedMap> getDependingMaps() {
69 return opposite.getDependingMaps();
70 }
71
72 @Override
73 public boolean isDirty() {
74 return opposite.isDirty();
75 }
76 }
77}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java
new file mode 100644
index 00000000..d09684df
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java
@@ -0,0 +1,32 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.tuple.Tuple;
13
14public class OppositeRefiner<A, C> extends AbstractPartialInterpretationRefiner<A, C> {
15 private final PartialInterpretationRefiner<A, C> opposite;
16
17 protected OppositeRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol,
18 PartialSymbol<A, C> oppositeSymbol) {
19 super(adapter, partialSymbol);
20 opposite = adapter.getRefiner(oppositeSymbol);
21 }
22
23 @Override
24 public boolean merge(Tuple key, A value) {
25 var oppositeKey = OppositeUtils.flip(key);
26 return opposite.merge(oppositeKey, value);
27 }
28
29 public static <A1, C1> Factory<A1, C1> of(PartialSymbol<A1, C1> oppositeSymbol) {
30 return (adapter, partialSymbol) -> new OppositeRefiner<>(adapter, partialSymbol, oppositeSymbol);
31 }
32}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java
new file mode 100644
index 00000000..6e15a628
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.ModalConstraint;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
21
22import java.util.List;
23import java.util.Set;
24
25public class OppositeRelationTranslator implements ModelStoreConfiguration, PartialRelationRewriter {
26 private final PartialRelation linkType;
27 private final PartialRelation opposite;
28
29 public OppositeRelationTranslator(PartialRelation linkType, PartialRelation opposite) {
30 if (linkType.arity() != 2) {
31 throw new TranslationException(linkType,
32 "Expected relation with opposite %s to have arity 2, got %d instead"
33 .formatted(linkType, linkType.arity()));
34 }
35 if (opposite.arity() != 2) {
36 throw new TranslationException(linkType,
37 "Expected opposite %s of %s to have arity 2, got %d instead"
38 .formatted(opposite, linkType, opposite.arity()));
39 }
40 this.linkType = linkType;
41 this.opposite = opposite;
42 }
43
44 @Override
45 public void apply(ModelStoreBuilder storeBuilder) {
46 storeBuilder.with(PartialRelationTranslator.of(linkType)
47 .rewriter(this)
48 .interpretation(OppositeInterpretation.of(opposite))
49 .refiner(OppositeRefiner.of(opposite))
50 .initializer(new RefinementBasedInitializer<>(linkType)));
51 }
52
53 @Override
54 public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal,
55 Modality modality, Concreteness concreteness) {
56 var arguments = literal.getArguments();
57 var newArguments = List.of(arguments.get(1), arguments.get(0));
58 var modalOpposite = new ModalConstraint(modality, concreteness, opposite);
59 var oppositeLiteral = literal.withArguments(modalOpposite, newArguments);
60 return List.of(oppositeLiteral);
61 }
62}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java
new file mode 100644
index 00000000..2a9e6b5d
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java
@@ -0,0 +1,22 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.tuple.Tuple2;
10
11final class OppositeUtils {
12 private OppositeUtils() {
13 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static Tuple flip(Tuple tuple) {
17 if (!(tuple instanceof Tuple2 tuple2)) {
18 throw new IllegalArgumentException("Cannot flip tuple: " + tuple);
19 }
20 return Tuple.of(tuple2.value1(), tuple2.value0());
21 }
22}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java
new file mode 100644
index 00000000..b401118e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java
@@ -0,0 +1,93 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.predicate;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.dnf.RelationalQuery;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.NodeVariable;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.view.ForbiddenView;
16import tools.refinery.store.query.view.MayView;
17import tools.refinery.store.query.view.MustView;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.TruthValue;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
26import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
27
28public class PredicateTranslator implements ModelStoreConfiguration {
29 private final PartialRelation relation;
30 private final RelationalQuery query;
31 private final boolean mutable;
32 private final TruthValue defaultValue;
33
34 public PredicateTranslator(PartialRelation relation, RelationalQuery query, boolean mutable,
35 TruthValue defaultValue) {
36 if (relation.arity() != query.arity()) {
37 throw new TranslationException(relation, "Expected arity %d query for partial relation %s, got %d instead"
38 .formatted(relation.arity(), relation, query.arity()));
39 }
40 if (defaultValue.must()) {
41 throw new TranslationException(relation, "Default value must be UNKNOWN or FALSE");
42 }
43 this.relation = relation;
44 this.query = query;
45 this.mutable = mutable;
46 this.defaultValue = defaultValue;
47 }
48
49 @Override
50 public void apply(ModelStoreBuilder storeBuilder) {
51 var translator = PartialRelationTranslator.of(relation)
52 .query(query);
53 if (mutable) {
54 var symbol = Symbol.of(relation.name(), relation.arity(), TruthValue.class, defaultValue);
55 translator.symbol(symbol);
56
57 var parameters = new NodeVariable[relation.arity()];
58 for (int i = 0; i < parameters.length; i++) {
59 parameters[i] = Variable.of("p" + i);
60 }
61
62 var must = Query.builder()
63 .parameters(parameters)
64 .clause(must(query.call(parameters)))
65 .clause(new MustView(symbol).call(parameters))
66 .build();
67 translator.must(must);
68
69 var mayLiterals = new Literal[2];
70 mayLiterals[0] = may(query.call(parameters));
71 if (defaultValue.may()) {
72 mayLiterals[1] = not(new ForbiddenView(symbol).call(parameters));
73 } else {
74 mayLiterals[1] = new MayView(symbol).call(parameters);
75 }
76 var may = Query.builder()
77 .parameters(parameters)
78 .clause(mayLiterals)
79 .build();
80 translator.may(may);
81 } else if (defaultValue.may()) {
82 // If all values are permitted, we don't need to check for any forbidden values in the model.
83 // If the result of this predicate of {@code ERROR}, some other partial relation (that we check for)
84 // will be {@code ERROR} as well.
85 translator.exclude(null);
86 translator.accept(null);
87 translator.objective(null);
88 } else {
89 translator.mayNever();
90 }
91 storeBuilder.with(translator);
92 }
93}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java
new file mode 100644
index 00000000..45dc5bd2
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java
@@ -0,0 +1,52 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.proxy;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.ModalConstraint;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19
20import java.util.List;
21import java.util.Set;
22
23public class PartialRelationTranslatorProxy implements ModelStoreConfiguration, PartialRelationRewriter {
24 private final PartialRelation partialRelation;
25 private final PartialRelation targetRelation;
26 private final boolean mutable;
27
28 public PartialRelationTranslatorProxy(PartialRelation partialRelation, PartialRelation targetRelation,
29 boolean mutable) {
30 this.partialRelation = partialRelation;
31 this.targetRelation = targetRelation;
32 this.mutable = mutable;
33 }
34
35 @Override
36 public void apply(ModelStoreBuilder storeBuilder) {
37 var translator = PartialRelationTranslator.of(partialRelation)
38 .interpretation(((adapter, concreteness, partialSymbol) ->
39 adapter.getPartialInterpretation(concreteness, targetRelation)))
40 .rewriter(this);
41 if (mutable) {
42 translator.refiner((adapter, partialSymbol) -> adapter.getRefiner(targetRelation));
43 }
44 storeBuilder.with(translator);
45 }
46
47 @Override
48 public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal,
49 Modality modality, Concreteness concreteness) {
50 return List.of(literal.withTarget(ModalConstraint.of(modality, concreteness, targetRelation)));
51 }
52}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java
new file mode 100644
index 00000000..faf1b958
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class CandidateTypeView extends InferredTypeView {
13 public CandidateTypeView(Symbol<InferredType> symbol, PartialRelation type) {
14 super(symbol, "candidate", type);
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredType value) {
19 return type.equals(value.candidateType());
20 }
21}
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
deleted file mode 100644
index 6e4728db..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java
+++ /dev/null
@@ -1,11 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10record EliminatedType(PartialRelation replacement) implements TypeAnalysisResult {
11}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java
deleted file mode 100644
index 40de4644..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java
+++ /dev/null
@@ -1,40 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Objects;
13
14class InferredMayTypeView extends TuplePreservingView<InferredType> {
15 private final PartialRelation type;
16
17 InferredMayTypeView(PartialRelation type) {
18 super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#may".formatted(type));
19 this.type = type;
20 }
21
22 @Override
23 protected boolean doFilter(Tuple key, InferredType value) {
24 return value.mayConcreteTypes().contains(type);
25 }
26
27 @Override
28 public boolean equals(Object o) {
29 if (this == o) return true;
30 if (o == null || getClass() != o.getClass()) return false;
31 if (!super.equals(o)) return false;
32 InferredMayTypeView that = (InferredMayTypeView) o;
33 return Objects.equals(type, that.type);
34 }
35
36 @Override
37 public int hashCode() {
38 return Objects.hash(super.hashCode(), type);
39 }
40}
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
index fd05158b..1ae94ae1 100644
--- 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
@@ -11,18 +11,18 @@ import java.util.Collections;
11import java.util.Set; 11import java.util.Set;
12 12
13record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, 13record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes,
14 PartialRelation currentType) { 14 PartialRelation candidateType) {
15 public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null); 15 public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null);
16 16
17 public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, 17 public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes,
18 PartialRelation currentType) { 18 PartialRelation candidateType) {
19 this.mustTypes = Collections.unmodifiableSet(mustTypes); 19 this.mustTypes = Collections.unmodifiableSet(mustTypes);
20 this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes); 20 this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes);
21 this.currentType = currentType; 21 this.candidateType = candidateType;
22 } 22 }
23 23
24 public boolean isConsistent() { 24 public boolean isConsistent() {
25 return currentType != null || mustTypes.isEmpty(); 25 return candidateType != null || mustTypes.isEmpty();
26 } 26 }
27 27
28 public boolean isMust(PartialRelation partialRelation) { 28 public boolean isMust(PartialRelation partialRelation) {
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java
new file mode 100644
index 00000000..40a7b3fa
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java
@@ -0,0 +1,38 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.tuple.Tuple;
15
16class InferredTypeRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> {
17 private final Interpretation<InferredType> interpretation;
18 private final TypeAnalysisResult result;
19
20 private InferredTypeRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol,
21 Symbol<InferredType> symbol, TypeAnalysisResult result) {
22 super(adapter, partialSymbol);
23 interpretation = adapter.getModel().getInterpretation(symbol);
24 this.result = result;
25 }
26
27 @Override
28 public boolean merge(Tuple key, TruthValue value) {
29 var currentType = interpretation.get(key);
30 var newType = result.merge(currentType, value);
31 interpretation.put(key, newType);
32 return true;
33 }
34
35 public static Factory<TruthValue, Boolean> of(Symbol<InferredType> symbol, TypeAnalysisResult result) {
36 return (adapter, partialSymbol) -> new InferredTypeRefiner(adapter, partialSymbol, symbol, result);
37 }
38}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java
index 1a121547..3c074df5 100644
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java
@@ -1,35 +1,30 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView; 8import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.Symbol;
11 11
12import java.util.Objects; 12import java.util.Objects;
13 13
14class InferredMustTypeView extends TuplePreservingView<InferredType> { 14abstract class InferredTypeView extends TuplePreservingView<InferredType> {
15 private final PartialRelation type; 15 protected final PartialRelation type;
16 16
17 InferredMustTypeView(PartialRelation type) { 17 protected InferredTypeView(Symbol<InferredType> symbol, String name, PartialRelation type) {
18 super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#must".formatted(type)); 18 super(symbol, type.name() + "#" + name);
19 this.type = type; 19 this.type = type;
20 } 20 }
21 21
22 @Override 22 @Override
23 protected boolean doFilter(Tuple key, InferredType value) {
24 return value.mustTypes().contains(type);
25 }
26
27 @Override
28 public boolean equals(Object o) { 23 public boolean equals(Object o) {
29 if (this == o) return true; 24 if (this == o) return true;
30 if (o == null || getClass() != o.getClass()) return false; 25 if (o == null || getClass() != o.getClass()) return false;
31 if (!super.equals(o)) return false; 26 if (!super.equals(o)) return false;
32 InferredMustTypeView that = (InferredMustTypeView) o; 27 InferredTypeView that = (InferredTypeView) o;
33 return Objects.equals(type, that.type); 28 return Objects.equals(type, that.type);
34 } 29 }
35 30
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java
new file mode 100644
index 00000000..dcaf61c5
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class MayTypeView extends InferredTypeView {
13 public MayTypeView(Symbol<InferredType> symbol, PartialRelation type) {
14 super(symbol, "may", type);
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredType value) {
19 return value.mayConcreteTypes().contains(type);
20 }
21}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java
new file mode 100644
index 00000000..833e1594
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java
@@ -0,0 +1,21 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class MustTypeView extends InferredTypeView {
13 public MustTypeView(Symbol<InferredType> symbol, PartialRelation type) {
14 super(symbol, "must", type);
15 }
16
17 @Override
18 protected boolean doFilter(Tuple key, InferredType value) {
19 return value.mustTypes().contains(type);
20 }
21}
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
deleted file mode 100644
index 0696f4c3..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java
+++ /dev/null
@@ -1,141 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.*;
12
13final class PreservedType implements TypeAnalysisResult {
14 private final ExtendedTypeInfo extendedTypeInfo;
15 private final List<PartialRelation> directSubtypes;
16 private final List<ExtendedTypeInfo> allExternalTypeInfoList;
17 private final InferredType inferredType;
18
19 public PreservedType(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) {
20 this.extendedTypeInfo = extendedTypeInfo;
21 directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes());
22 this.allExternalTypeInfoList = allExternalTypeInfoList;
23 inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(),
24 extendedTypeInfo.getConcreteSubtypesAndSelf());
25 }
26
27 public PartialRelation type() {
28 return extendedTypeInfo.getType();
29 }
30
31 public List<PartialRelation> getDirectSubtypes() {
32 return directSubtypes;
33 }
34
35 public boolean isAbstractType() {
36 return extendedTypeInfo.isAbstractType();
37 }
38
39 public boolean isVacuous() {
40 return isAbstractType() && directSubtypes.isEmpty();
41 }
42
43 public InferredType asInferredType() {
44 return inferredType;
45 }
46
47 public InferredType merge(InferredType inferredType, TruthValue value) {
48 return switch (value) {
49 case UNKNOWN -> inferredType;
50 case TRUE -> addMust(inferredType);
51 case FALSE -> removeMay(inferredType);
52 case ERROR -> addError(inferredType);
53 };
54 }
55
56 private InferredType addMust(InferredType inferredType) {
57 var originalMustTypes = inferredType.mustTypes();
58 if (originalMustTypes.contains(type())) {
59 return inferredType;
60 }
61 var mustTypes = new HashSet<>(originalMustTypes);
62 extendedTypeInfo.addMust(mustTypes);
63 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
64 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
65 Set<PartialRelation> mayConcreteTypesResult;
66 if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
67 mayConcreteTypesResult = mayConcreteTypes;
68 } else {
69 mayConcreteTypesResult = originalMayConcreteTypes;
70 }
71 return propagateMust(mustTypes, mayConcreteTypesResult);
72 }
73
74 private InferredType removeMay(InferredType inferredType) {
75 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
76 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
77 if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
78 return inferredType;
79 }
80 return propagateMust(inferredType.mustTypes(), mayConcreteTypes);
81 }
82
83 private InferredType addError(InferredType inferredType) {
84 var originalMustTypes = inferredType.mustTypes();
85 if (originalMustTypes.contains(type())) {
86 if (inferredType.mayConcreteTypes().isEmpty()) {
87 return inferredType;
88 }
89 return new InferredType(originalMustTypes, Set.of(), null);
90 }
91 var mustTypes = new HashSet<>(originalMustTypes);
92 extendedTypeInfo.addMust(mustTypes);
93 return new InferredType(mustTypes, Set.of(), null);
94 }
95
96 private InferredType propagateMust(Set<PartialRelation> originalMustTypes,
97 Set<PartialRelation> mayConcreteTypes) {
98 // It is possible that there is not type at all, do not force one by propagation.
99 var maybeUntyped = originalMustTypes.isEmpty();
100 // Para-consistent case, do not propagate must types to avoid logical explosion.
101 var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty();
102 if (maybeUntyped || paraConsistentOrSurelyUntyped) {
103 return new InferredType(originalMustTypes, mayConcreteTypes, null);
104 }
105 var currentType = computeCurrentType(mayConcreteTypes);
106 var mustTypes = new HashSet<>(originalMustTypes);
107 boolean changed = false;
108 for (var newMustExtendedTypeInfo : allExternalTypeInfoList) {
109 var newMustType = newMustExtendedTypeInfo.getType();
110 if (mustTypes.contains(newMustType)) {
111 continue;
112 }
113 if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) {
114 newMustExtendedTypeInfo.addMust(mustTypes);
115 changed = true;
116 }
117 }
118 if (!changed) {
119 return new InferredType(originalMustTypes, mayConcreteTypes, currentType);
120 }
121 return new InferredType(mustTypes, mayConcreteTypes, currentType);
122 }
123
124 /**
125 * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types.
126 *
127 * @param mayConcreteTypes The set of allowed concrete types. Must not be empty.
128 * @return The first concrete type that is allowed by {@code matConcreteTypes}.
129 */
130 private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) {
131 for (var concreteExtendedTypeInfo : allExternalTypeInfoList) {
132 var concreteType = concreteExtendedTypeInfo.getType();
133 if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) {
134 return concreteType;
135 }
136 }
137 // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>,
138 // Set<PartialRelation>}.
139 throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes));
140 }
141}
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
index fbf8a7c9..ebe0d1b9 100644
--- 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
@@ -5,5 +5,141 @@
5 */ 5 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8sealed interface TypeAnalysisResult permits EliminatedType, PreservedType { 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.*;
12
13public final class TypeAnalysisResult {
14 private final ExtendedTypeInfo extendedTypeInfo;
15 private final List<PartialRelation> directSubtypes;
16 private final List<ExtendedTypeInfo> allExternalTypeInfoList;
17 private final InferredType inferredType;
18
19 public TypeAnalysisResult(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) {
20 this.extendedTypeInfo = extendedTypeInfo;
21 directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes());
22 this.allExternalTypeInfoList = allExternalTypeInfoList;
23 inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(),
24 extendedTypeInfo.getConcreteSubtypesAndSelf());
25 }
26
27 public PartialRelation type() {
28 return extendedTypeInfo.getType();
29 }
30
31 public List<PartialRelation> getDirectSubtypes() {
32 return directSubtypes;
33 }
34
35 public boolean isAbstractType() {
36 return extendedTypeInfo.isAbstractType();
37 }
38
39 public boolean isVacuous() {
40 return isAbstractType() && directSubtypes.isEmpty();
41 }
42
43 public InferredType asInferredType() {
44 return inferredType;
45 }
46
47 public boolean isSubtypeOf(TypeAnalysisResult other) {
48 return extendedTypeInfo.getAllSubtypes().contains(other.type());
49 }
50
51 public InferredType merge(InferredType inferredType, TruthValue value) {
52 return switch (value) {
53 case UNKNOWN -> inferredType;
54 case TRUE -> addMust(inferredType);
55 case FALSE -> removeMay(inferredType);
56 case ERROR -> addError(inferredType);
57 };
58 }
59
60 private InferredType addMust(InferredType inferredType) {
61 var originalMustTypes = inferredType.mustTypes();
62 if (originalMustTypes.contains(type())) {
63 return inferredType;
64 }
65 var mustTypes = new HashSet<>(originalMustTypes);
66 extendedTypeInfo.addMust(mustTypes);
67 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
68 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
69 Set<PartialRelation> mayConcreteTypesResult;
70 if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
71 mayConcreteTypesResult = mayConcreteTypes;
72 } else {
73 mayConcreteTypesResult = originalMayConcreteTypes;
74 }
75 return propagateMust(mustTypes, mayConcreteTypesResult);
76 }
77
78 private InferredType removeMay(InferredType inferredType) {
79 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
80 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
81 if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
82 return inferredType;
83 }
84 return propagateMust(inferredType.mustTypes(), mayConcreteTypes);
85 }
86
87 private InferredType addError(InferredType inferredType) {
88 var originalMustTypes = inferredType.mustTypes();
89 if (originalMustTypes.contains(type())) {
90 if (inferredType.mayConcreteTypes().isEmpty()) {
91 return inferredType;
92 }
93 return new InferredType(originalMustTypes, Set.of(), null);
94 }
95 var mustTypes = new HashSet<>(originalMustTypes);
96 extendedTypeInfo.addMust(mustTypes);
97 return new InferredType(mustTypes, Set.of(), null);
98 }
99
100 private InferredType propagateMust(Set<PartialRelation> originalMustTypes,
101 Set<PartialRelation> mayConcreteTypes) {
102 // It is possible that there is not type at all, do not force one by propagation.
103 var maybeUntyped = originalMustTypes.isEmpty();
104 // Para-consistent case, do not propagate must types to avoid logical explosion.
105 var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty();
106 if (maybeUntyped || paraConsistentOrSurelyUntyped) {
107 return new InferredType(originalMustTypes, mayConcreteTypes, null);
108 }
109 var currentType = computeCurrentType(mayConcreteTypes);
110 var mustTypes = new HashSet<>(originalMustTypes);
111 boolean changed = false;
112 for (var newMustExtendedTypeInfo : allExternalTypeInfoList) {
113 var newMustType = newMustExtendedTypeInfo.getType();
114 if (mustTypes.contains(newMustType)) {
115 continue;
116 }
117 if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) {
118 newMustExtendedTypeInfo.addMust(mustTypes);
119 changed = true;
120 }
121 }
122 if (!changed) {
123 return new InferredType(originalMustTypes, mayConcreteTypes, currentType);
124 }
125 return new InferredType(mustTypes, mayConcreteTypes, currentType);
126 }
127
128 /**
129 * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types.
130 *
131 * @param mayConcreteTypes The set of allowed concrete types. Must not be empty.
132 * @return The first concrete type that is allowed by {@code matConcreteTypes}.
133 */
134 private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) {
135 for (var concreteExtendedTypeInfo : allExternalTypeInfoList) {
136 var concreteType = concreteExtendedTypeInfo.getType();
137 if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) {
138 return concreteType;
139 }
140 }
141 // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>,
142 // Set<PartialRelation>}.
143 throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes));
144 }
9} 145}
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/TypeHierarchy.java
index e97ce954..3f918c97 100644
--- 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/TypeHierarchy.java
@@ -6,17 +6,20 @@
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9 10
10import java.util.*; 11import java.util.*;
11 12
12class TypeAnalyzer { 13public class TypeHierarchy {
14 private final Set<PartialRelation> allTypes;
13 private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap; 15 private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap;
14 private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>(); 16 private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>();
15 private final InferredType unknownType; 17 private final InferredType unknownType;
16 private final Map<PartialRelation, TypeAnalysisResult> analysisResults; 18 private final Map<PartialRelation, TypeAnalysisResult> preservedTypes;
17 19
18 public TypeAnalyzer(Map<PartialRelation, TypeInfo> typeInfoMap) { 20 TypeHierarchy(Map<PartialRelation, TypeInfo> typeInfoMap) {
19 int size = typeInfoMap.size(); 21 int size = typeInfoMap.size();
22 allTypes = Collections.unmodifiableSet(new LinkedHashSet<>(typeInfoMap.keySet()));
20 extendedTypeInfoMap = new LinkedHashMap<>(size); 23 extendedTypeInfoMap = new LinkedHashMap<>(size);
21 var concreteTypes = new LinkedHashSet<PartialRelation>(); 24 var concreteTypes = new LinkedHashSet<PartialRelation>();
22 int index = 0; 25 int index = 0;
@@ -34,15 +37,39 @@ class TypeAnalyzer {
34 computeAllAndConcreteSubtypes(); 37 computeAllAndConcreteSubtypes();
35 computeDirectSubtypes(); 38 computeDirectSubtypes();
36 eliminateTrivialSupertypes(); 39 eliminateTrivialSupertypes();
37 analysisResults = computeAnalysisResults(); 40 preservedTypes = computeAnalysisResults();
41 }
42
43 public boolean isEmpty() {
44 return extendedTypeInfoMap.isEmpty();
38 } 45 }
39 46
40 public InferredType getUnknownType() { 47 public InferredType getUnknownType() {
41 return unknownType; 48 return unknownType;
42 } 49 }
43 50
44 public Map<PartialRelation, TypeAnalysisResult> getAnalysisResults() { 51 public Set<PartialRelation> getAllTypes() {
45 return analysisResults; 52 return allTypes;
53 }
54
55 public Map<PartialRelation, TypeAnalysisResult> getPreservedTypes() {
56 return preservedTypes;
57 }
58
59 public Map<PartialRelation, PartialRelation> getEliminatedTypes() {
60 return Collections.unmodifiableMap(replacements);
61 }
62
63 public TypeAnalysisResult getAnalysisResult(PartialRelation type) {
64 var preservedResult = preservedTypes.get(type);
65 if (preservedResult != null) {
66 return preservedResult;
67 }
68 var eliminatedResult = replacements.get(type);
69 if (eliminatedResult != null) {
70 return preservedTypes.get(eliminatedResult);
71 }
72 throw new IllegalArgumentException("Unknown type: " + type);
46 } 73 }
47 74
48 private void computeAllSupertypes() { 75 private void computeAllSupertypes() {
@@ -53,7 +80,13 @@ class TypeAnalyzer {
53 var found = new HashSet<PartialRelation>(); 80 var found = new HashSet<PartialRelation>();
54 var allSupertypes = extendedTypeInfo.getAllSupertypes(); 81 var allSupertypes = extendedTypeInfo.getAllSupertypes();
55 for (var supertype : allSupertypes) { 82 for (var supertype : allSupertypes) {
56 found.addAll(extendedTypeInfoMap.get(supertype).getAllSupertypes()); 83 var supertypeInfo = extendedTypeInfoMap.get(supertype);
84 if (supertypeInfo == null) {
85 throw new TranslationException(extendedTypeInfo.getType(),
86 "Supertype %s of %s is missing from the type hierarchy"
87 .formatted(supertype, extendedTypeInfo.getType()));
88 }
89 found.addAll(supertypeInfo.getAllSupertypes());
57 } 90 }
58 if (allSupertypes.addAll(found)) { 91 if (allSupertypes.addAll(found)) {
59 changed = true; 92 changed = true;
@@ -70,7 +103,7 @@ class TypeAnalyzer {
70 } 103 }
71 for (var supertype : extendedTypeInfo.getAllSupertypes()) { 104 for (var supertype : extendedTypeInfo.getAllSupertypes()) {
72 if (type.equals(supertype)) { 105 if (type.equals(supertype)) {
73 throw new IllegalArgumentException("%s cannot be a supertype of itself".formatted(type)); 106 throw new TranslationException(type, "%s cannot be a supertype of itself".formatted(type));
74 } 107 }
75 var supertypeInfo = extendedTypeInfoMap.get(supertype); 108 var supertypeInfo = extendedTypeInfoMap.get(supertype);
76 supertypeInfo.getAllSubtypes().add(type); 109 supertypeInfo.getAllSubtypes().add(type);
@@ -95,25 +128,37 @@ class TypeAnalyzer {
95 } 128 }
96 129
97 private void eliminateTrivialSupertypes() { 130 private void eliminateTrivialSupertypes() {
98 boolean changed; 131 Set<PartialRelation> toInspect = new HashSet<>(extendedTypeInfoMap.keySet());
99 do { 132 while (!toInspect.isEmpty()) {
100 var toRemove = new ArrayList<PartialRelation>(); 133 var toRemove = new ArrayList<PartialRelation>();
101 for (var entry : extendedTypeInfoMap.entrySet()) { 134 for (var partialRelation : toInspect) {
102 var extendedTypeInfo = entry.getValue(); 135 var extendedTypeInfo = extendedTypeInfoMap.get(partialRelation);
103 boolean isAbstract = extendedTypeInfo.isAbstractType(); 136 if (extendedTypeInfo != null && isTrivialSupertype(extendedTypeInfo)) {
104 // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e., 137 toRemove.add(partialRelation);
105 // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist.
106 boolean hasSingleDirectSubtype = extendedTypeInfo.getDirectSubtypes().size() == 1;
107 if (isAbstract && hasSingleDirectSubtype) {
108 toRemove.add(entry.getKey());
109 } 138 }
110 } 139 }
111 toRemove.forEach(this::removeTrivialType); 140 toInspect.clear();
112 changed = !toRemove.isEmpty(); 141 for (var partialRelation : toRemove) {
113 } while (changed); 142 removeTrivialType(partialRelation, toInspect);
143 }
144 }
145 }
146
147 private boolean isTrivialSupertype(ExtendedTypeInfo extendedTypeInfo) {
148 if (!extendedTypeInfo.isAbstractType()) {
149 return false;
150 }
151 var subtypeIterator = extendedTypeInfo.getDirectSubtypes().iterator();
152 if (!subtypeIterator.hasNext()) {
153 // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e.,
154 // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist.
155 return false;
156 }
157 var directSubtype = subtypeIterator.next();
158 return !extendedTypeInfoMap.get(directSubtype).isAbstractType() && !subtypeIterator.hasNext();
114 } 159 }
115 160
116 private void removeTrivialType(PartialRelation trivialType) { 161 private void removeTrivialType(PartialRelation trivialType, Set<PartialRelation> toInspect) {
117 var extendedTypeInfo = extendedTypeInfoMap.get(trivialType); 162 var extendedTypeInfo = extendedTypeInfoMap.get(trivialType);
118 var iterator = extendedTypeInfo.getDirectSubtypes().iterator(); 163 var iterator = extendedTypeInfo.getDirectSubtypes().iterator();
119 if (!iterator.hasNext()) { 164 if (!iterator.hasNext()) {
@@ -125,7 +170,6 @@ class TypeAnalyzer {
125 throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype" 170 throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype"
126 .formatted(trivialType)); 171 .formatted(trivialType));
127 } 172 }
128 replacements.put(trivialType, replacement);
129 for (var supertype : extendedTypeInfo.getAllSupertypes()) { 173 for (var supertype : extendedTypeInfo.getAllSupertypes()) {
130 var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype); 174 var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype);
131 if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) { 175 if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) {
@@ -134,6 +178,9 @@ class TypeAnalyzer {
134 var directSubtypes = extendedSupertypeInfo.getDirectSubtypes(); 178 var directSubtypes = extendedSupertypeInfo.getDirectSubtypes();
135 if (directSubtypes.remove(trivialType)) { 179 if (directSubtypes.remove(trivialType)) {
136 directSubtypes.add(replacement); 180 directSubtypes.add(replacement);
181 if (extendedSupertypeInfo.isAbstractType() && directSubtypes.size() == 1) {
182 toInspect.add(supertype);
183 }
137 } 184 }
138 } 185 }
139 for (var subtype : extendedTypeInfo.getAllSubtypes()) { 186 for (var subtype : extendedTypeInfo.getAllSubtypes()) {
@@ -156,17 +203,13 @@ class TypeAnalyzer {
156 203
157 private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() { 204 private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() {
158 var allExtendedTypeInfoList = sortTypes(); 205 var allExtendedTypeInfoList = sortTypes();
159 var results = new LinkedHashMap<PartialRelation, TypeAnalysisResult>( 206 var preservedResults = new LinkedHashMap<PartialRelation, TypeAnalysisResult>(
160 allExtendedTypeInfoList.size() + replacements.size()); 207 allExtendedTypeInfoList.size());
161 for (var extendedTypeInfo : allExtendedTypeInfoList) { 208 for (var extendedTypeInfo : allExtendedTypeInfoList) {
162 var type = extendedTypeInfo.getType(); 209 var type = extendedTypeInfo.getType();
163 results.put(type, new PreservedType(extendedTypeInfo, allExtendedTypeInfoList)); 210 preservedResults.put(type, new TypeAnalysisResult(extendedTypeInfo, allExtendedTypeInfoList));
164 }
165 for (var entry : replacements.entrySet()) {
166 var type = entry.getKey();
167 results.put(type, new EliminatedType(entry.getValue()));
168 } 211 }
169 return Collections.unmodifiableMap(results); 212 return Collections.unmodifiableMap(preservedResults);
170 } 213 }
171 214
172 private List<ExtendedTypeInfo> sortTypes() { 215 private List<ExtendedTypeInfo> sortTypes() {
@@ -204,4 +247,8 @@ class TypeAnalyzer {
204 } 247 }
205 return Collections.unmodifiableList(sorted); 248 return Collections.unmodifiableList(sorted);
206 } 249 }
250
251 public static TypeHierarchyBuilder builder() {
252 return new TypeHierarchyBuilder();
253 }
207} 254}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java
new file mode 100644
index 00000000..ce8fda05
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java
@@ -0,0 +1,66 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10
11import java.util.*;
12
13@SuppressWarnings("UnusedReturnValue")
14public class TypeHierarchyBuilder {
15 protected final Map<PartialRelation, TypeInfo> typeInfoMap = new LinkedHashMap<>();
16
17 protected TypeHierarchyBuilder() {
18 }
19
20 public TypeHierarchyBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) {
21 if (partialRelation.arity() != 1) {
22 throw new TranslationException(partialRelation,
23 "Only types of arity 1 are supported, got %s with %d instead"
24 .formatted(partialRelation, partialRelation.arity()));
25 }
26 var putResult = typeInfoMap.put(partialRelation, typeInfo);
27 if (putResult != null && !putResult.equals(typeInfo)) {
28 throw new TranslationException(partialRelation,
29 "Duplicate type info for partial relation: " + partialRelation);
30 }
31 return this;
32 }
33
34 public TypeHierarchyBuilder type(PartialRelation partialRelation, boolean abstractType,
35 PartialRelation... supertypes) {
36 return type(partialRelation, abstractType, Set.of(supertypes));
37 }
38
39 public TypeHierarchyBuilder type(PartialRelation partialRelation, boolean abstractType,
40 Collection<PartialRelation> supertypes) {
41 return type(partialRelation, new TypeInfo(supertypes, abstractType));
42 }
43
44 public TypeHierarchyBuilder type(PartialRelation partialRelation, PartialRelation... supertypes) {
45 return type(partialRelation, List.of(supertypes));
46 }
47
48 public TypeHierarchyBuilder type(PartialRelation partialRelation, Collection<PartialRelation> supertypes) {
49 return type(partialRelation, false, supertypes);
50 }
51
52 public TypeHierarchyBuilder types(Collection<Map.Entry<PartialRelation, TypeInfo>> entries) {
53 for (var entry : entries) {
54 type(entry.getKey(), entry.getValue());
55 }
56 return this;
57 }
58
59 public TypeHierarchyBuilder types(Map<PartialRelation, TypeInfo> map) {
60 return types(map.entrySet());
61 }
62
63 public TypeHierarchy build() {
64 return new TypeHierarchy(typeInfoMap);
65 }
66}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java
new file mode 100644
index 00000000..c74f1e78
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java
@@ -0,0 +1,61 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.Arrays;
17import java.util.HashMap;
18import java.util.function.Function;
19
20public class TypeHierarchyInitializer implements PartialModelInitializer {
21 private final TypeHierarchy typeHierarchy;
22 private final Symbol<InferredType> typeSymbol;
23
24 public TypeHierarchyInitializer(TypeHierarchy typeHierarchy, Symbol<InferredType> typeSymbol) {
25 this.typeHierarchy = typeHierarchy;
26 this.typeSymbol = typeSymbol;
27 }
28
29 @Override
30 public void initialize(Model model, ModelSeed modelSeed) {
31 var inferredTypes = new InferredType[modelSeed.getNodeCount()];
32 Arrays.fill(inferredTypes, typeHierarchy.getUnknownType());
33 for (var type : typeHierarchy.getAllTypes()) {
34 initializeType(type, inferredTypes, modelSeed);
35 }
36 var typeInterpretation = model.getInterpretation(typeSymbol);
37 var uniqueTable = new HashMap<InferredType, InferredType>();
38 for (int i = 0; i < inferredTypes.length; i++) {
39 var uniqueType = uniqueTable.computeIfAbsent(inferredTypes[i], Function.identity());
40 typeInterpretation.put(Tuple.of(i), uniqueType);
41 }
42 }
43
44 private void initializeType(PartialRelation type, InferredType[] inferredTypes, ModelSeed modelSeed) {
45 var cursor = modelSeed.getCursor(type, TruthValue.UNKNOWN);
46 var analysisResult = typeHierarchy.getAnalysisResult(type);
47 while (cursor.move()) {
48 var i = cursor.getKey().get(0);
49 checkNodeId(inferredTypes, i);
50 var value = cursor.getValue();
51 inferredTypes[i] = analysisResult.merge(inferredTypes[i], value);
52 }
53 }
54
55 private void checkNodeId(InferredType[] inferredTypes, int nodeId) {
56 if (nodeId < 0 || nodeId >= inferredTypes.length) {
57 throw new IllegalArgumentException("Expected node id %d to be lower than model size %d"
58 .formatted(nodeId, inferredTypes.length));
59 }
60 }
61}
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
deleted file mode 100644
index 06e3c05f..00000000
--- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java
+++ /dev/null
@@ -1,37 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslatedRelation;
11import tools.refinery.store.reasoning.translator.TranslationUnit;
12import tools.refinery.store.representation.Symbol;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Map;
17
18public class TypeHierarchyTranslationUnit extends TranslationUnit {
19 static final Symbol<InferredType> INFERRED_TYPE_SYMBOL = Symbol.of(
20 "inferredType", 1, InferredType.class, InferredType.UNTYPED);
21
22 private final TypeAnalyzer typeAnalyzer;
23
24 public TypeHierarchyTranslationUnit(Map<PartialRelation, TypeInfo> typeInfoMap) {
25 typeAnalyzer = new TypeAnalyzer(typeInfoMap);
26 }
27
28 @Override
29 public Collection<TranslatedRelation> getTranslatedRelations() {
30 return List.of();
31 }
32
33 @Override
34 public void initializeModel(Model model, int nodeCount) {
35
36 }
37}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java
new file mode 100644
index 00000000..dc8a1d36
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java
@@ -0,0 +1,110 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.dse.transition.actions.ActionLiteral;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.model.ModelStoreConfiguration;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.reasoning.ReasoningBuilder;
14import tools.refinery.store.reasoning.actions.PartialActionLiterals;
15import tools.refinery.store.reasoning.literal.PartialLiterals;
16import tools.refinery.store.reasoning.representation.PartialRelation;
17import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
19import tools.refinery.store.reasoning.translator.proxy.PartialRelationTranslatorProxy;
20import tools.refinery.store.representation.Symbol;
21
22import java.util.ArrayList;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust;
26import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
27
28public class TypeHierarchyTranslator implements ModelStoreConfiguration {
29 private final Symbol<InferredType> typeSymbol = Symbol.of("TYPE", 1, InferredType.class, InferredType.UNTYPED);
30 private final TypeHierarchy typeHierarchy;
31
32 public TypeHierarchyTranslator(TypeHierarchy typeHierarchy) {
33 this.typeHierarchy = typeHierarchy;
34 }
35
36 @Override
37 public void apply(ModelStoreBuilder storeBuilder) {
38 if (typeHierarchy.isEmpty()) {
39 return;
40 }
41
42 storeBuilder.symbol(typeSymbol);
43
44 for (var entry : typeHierarchy.getPreservedTypes().entrySet()) {
45 storeBuilder.with(createPreservedTypeTranslator(entry.getKey(), entry.getValue()));
46 }
47
48 for (var entry : typeHierarchy.getEliminatedTypes().entrySet()) {
49 storeBuilder.with(createEliminatedTypeTranslator(entry.getKey(), entry.getValue()));
50 }
51
52 var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class);
53 reasoningBuilder.initializer(new TypeHierarchyInitializer(typeHierarchy, typeSymbol));
54 }
55
56 private ModelStoreConfiguration createPreservedTypeTranslator(PartialRelation type, TypeAnalysisResult result) {
57 var may = Query.of(type.name() + "#partial#may", (builder, p1) -> {
58 if (!result.isAbstractType()) {
59 builder.clause(new MayTypeView(typeSymbol, type).call(p1));
60 }
61 for (var subtype : result.getDirectSubtypes()) {
62 builder.clause(may(subtype.call(p1)));
63 }
64 });
65
66 var must = Query.of(type.name() + "#partial#must", (builder, p1) -> builder.clause(
67 new MustTypeView(typeSymbol, type).call(p1)
68 ));
69
70 var candidate = Query.of(type.name() + "#candidate", (builder, p1) -> {
71 if (!result.isAbstractType()) {
72 builder.clause(new CandidateTypeView(typeSymbol, type).call(p1));
73 }
74 for (var subtype : result.getDirectSubtypes()) {
75 builder.clause(PartialLiterals.candidateMust(subtype.call(p1)));
76 }
77 });
78
79 var translator = PartialRelationTranslator.of(type)
80 .may(may)
81 .must(must)
82 .candidate(candidate)
83 .refiner(InferredTypeRefiner.of(typeSymbol, result));
84
85 if (!result.isAbstractType()) {
86 var decision = Rule.of(type.name(), (builder, instance) -> builder
87 .clause(
88 may(type.call(instance)),
89 not(candidateMust(type.call(instance))),
90 not(MultiObjectTranslator.MULTI_VIEW.call(instance))
91 )
92 .action(() -> {
93 var actionLiterals = new ArrayList<ActionLiteral>();
94 actionLiterals.add(PartialActionLiterals.add(type, instance));
95 for (var subtype : result.getDirectSubtypes()) {
96 actionLiterals.add(PartialActionLiterals.remove(subtype, instance));
97 }
98 return actionLiterals;
99 }));
100 translator.decision(decision);
101 }
102
103 return translator;
104 }
105
106 private ModelStoreConfiguration createEliminatedTypeTranslator(
107 PartialRelation type, PartialRelation replacement) {
108 return new PartialRelationTranslatorProxy(type, replacement, true);
109 }
110}
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
index 9f897e46..e6bdaff2 100644
--- 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
@@ -9,43 +9,15 @@ import tools.refinery.store.reasoning.representation.PartialRelation;
9 9
10import java.util.*; 10import java.util.*;
11 11
12public record TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) { 12public record TypeInfo(Set<PartialRelation> supertypes, boolean abstractType) {
13 public static Builder builder() { 13 public TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) {
14 return new Builder(); 14 this(Set.copyOf(supertypes), abstractType);
15 } 15 }
16 16
17 public static class Builder { 17 public TypeInfo addSupertype(PartialRelation newSupertype) {
18 private final Set<PartialRelation> supertypes = new LinkedHashSet<>(); 18 var newSupertypes = new ArrayList<PartialRelation>(supertypes.size() + 1);
19 private boolean abstractType; 19 newSupertypes.addAll(supertypes);
20 20 newSupertypes.add(newSupertype);
21 private Builder() { 21 return new TypeInfo(newSupertypes, abstractType);
22 }
23
24 public Builder supertypes(Collection<PartialRelation> supertypes) {
25 this.supertypes.addAll(supertypes);
26 return this;
27 }
28
29 public Builder supertypes(PartialRelation... supertypes) {
30 return supertypes(List.of(supertypes));
31 }
32
33 public Builder supertype(PartialRelation supertype) {
34 supertypes.add(supertype);
35 return this;
36 }
37
38 public Builder abstractType(boolean abstractType) {
39 this.abstractType = abstractType;
40 return this;
41 }
42
43 public Builder abstractType() {
44 return abstractType(true);
45 }
46
47 public TypeInfo build() {
48 return new TypeInfo(Collections.unmodifiableSet(supertypes), abstractType);
49 }
50 } 22 }
51} 23}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java
new file mode 100644
index 00000000..77560a68
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java
@@ -0,0 +1,108 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
14import tools.refinery.store.query.view.ForbiddenView;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.representation.PartialRelation;
17import tools.refinery.store.reasoning.seed.ModelSeed;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
20import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue;
22import tools.refinery.store.tuple.Tuple;
23
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.is;
26import static org.hamcrest.Matchers.not;
27import static org.hamcrest.Matchers.nullValue;
28import static tools.refinery.store.query.literal.Literals.not;
29import static tools.refinery.store.reasoning.ReasoningAdapter.EQUALS_SYMBOL;
30import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL;
31import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
32import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
33
34class PartialModelTest {
35 @Test
36 void partialModelTest() {
37 var person = new PartialRelation("Person", 1);
38 var friend = new PartialRelation("friend", 2);
39 var lonely = new PartialRelation("lonely", 1);
40
41 var personStorage = Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE);
42 var friendStorage = Symbol.of("friend", 2, TruthValue.class, TruthValue.UNKNOWN);
43
44 var store = ModelStore.builder()
45 .with(ViatraModelQueryAdapter.builder())
46 .with(ReasoningAdapter.builder())
47 .with(new MultiObjectTranslator())
48 .with(PartialRelationTranslator.of(person)
49 .symbol(personStorage))
50 .with(PartialRelationTranslator.of(friend)
51 .symbol(friendStorage)
52 .may(Query.of("mayFriend", (builder, p1, p2) -> builder.clause(
53 may(person.call(p1)),
54 may(person.call(p2)),
55 not(must(EQUALS_SYMBOL.call(p1, p2))),
56 not(new ForbiddenView(friendStorage).call(p1, p2))
57 ))))
58 .with(PartialRelationTranslator.of(lonely)
59 .query(Query.of("lonely", (builder, p1) -> builder.clause(
60 person.call(p1),
61 not(friend.call(p1, Variable.of())))
62 )))
63 .build();
64
65 var modelSeed = ModelSeed.builder(4)
66 .seed(EXISTS_SYMBOL, builder -> builder
67 .put(Tuple.of(0), TruthValue.TRUE)
68 .put(Tuple.of(1), TruthValue.UNKNOWN)
69 .put(Tuple.of(2), TruthValue.TRUE)
70 .put(Tuple.of(3), TruthValue.TRUE))
71 .seed(EQUALS_SYMBOL, builder -> builder
72 .put(Tuple.of(0, 0), TruthValue.TRUE)
73 .put(Tuple.of(1, 1), TruthValue.UNKNOWN)
74 .put(Tuple.of(2, 2), TruthValue.UNKNOWN)
75 .put(Tuple.of(3, 3), TruthValue.TRUE))
76 .seed(person, builder -> builder
77 .put(Tuple.of(0), TruthValue.TRUE)
78 .put(Tuple.of(1), TruthValue.TRUE)
79 .put(Tuple.of(2), TruthValue.UNKNOWN))
80 .seed(friend, builder -> builder
81 .reducedValue(TruthValue.UNKNOWN)
82 .put(Tuple.of(0, 1), TruthValue.TRUE)
83 .put(Tuple.of(1, 2), TruthValue.FALSE))
84 .build();
85 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
86
87 var queryAdapter = model.getAdapter(ModelQueryAdapter.class);
88 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
89 var friendInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, friend);
90 var friendRefiner = reasoningAdapter.getRefiner(friend);
91
92 assertThat(friendInterpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE));
93 assertThat(friendInterpretation.get(Tuple.of(1, 0)), is(TruthValue.UNKNOWN));
94 assertThat(friendInterpretation.get(Tuple.of(3, 0)), is(TruthValue.FALSE));
95
96 assertThat(friendRefiner.merge(Tuple.of(0, 1), TruthValue.FALSE), is(true));
97 assertThat(friendRefiner.merge(Tuple.of(1, 0), TruthValue.TRUE), is(true));
98 var splitResult = reasoningAdapter.split(1);
99 assertThat(splitResult, not(nullValue()));
100 var newPerson = splitResult.get(0);
101 queryAdapter.flushChanges();
102
103 assertThat(friendInterpretation.get(Tuple.of(0, 1)), is(TruthValue.ERROR));
104 assertThat(friendInterpretation.get(Tuple.of(1, 0)), is(TruthValue.TRUE));
105 assertThat(friendInterpretation.get(Tuple.of(0, newPerson)), is(TruthValue.ERROR));
106 assertThat(friendInterpretation.get(Tuple.of(newPerson, 0)), is(TruthValue.TRUE));
107 }
108}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java
new file mode 100644
index 00000000..793d1cec
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java
@@ -0,0 +1,395 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.lifting;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.view.AnySymbolView;
14import tools.refinery.store.query.view.FunctionView;
15import tools.refinery.store.query.view.MustView;
16import tools.refinery.store.reasoning.ReasoningAdapter;
17import tools.refinery.store.reasoning.literal.Concreteness;
18import tools.refinery.store.reasoning.literal.ModalConstraint;
19import tools.refinery.store.reasoning.literal.Modality;
20import tools.refinery.store.reasoning.representation.PartialRelation;
21import tools.refinery.store.reasoning.representation.PartialSymbol;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24
25import java.util.List;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static tools.refinery.store.query.literal.Literals.check;
29import static tools.refinery.store.query.literal.Literals.not;
30import static tools.refinery.store.query.term.int_.IntTerms.*;
31import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
32
33class DnfLifterTest {
34 private static final Symbol<TruthValue> friendSymbol = Symbol.of("friend", 2, TruthValue.class,
35 TruthValue.UNKNOWN);
36 private static final AnySymbolView friendMustView = new MustView(friendSymbol);
37 private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class);
38 private static final FunctionView<Integer> ageView = new FunctionView<>(age);
39 private static final PartialRelation person = PartialSymbol.of("Person", 1);
40 private static final PartialRelation friend = PartialSymbol.of("friend", 2);
41
42 private DnfLifter sut;
43
44 @BeforeEach
45 void beforeEach() {
46 sut = new DnfLifter();
47 }
48
49 @Test
50 void liftPartialRelationCallTest() {
51 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
52 friend.call(p1, v1)
53 ))).getDnf();
54 var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input);
55
56 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
57 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, v1),
58 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1)
59 ))).getDnf();
60
61 assertThat(actual, structurallyEqualTo(expected));
62 }
63
64 @Test
65 void liftPartialDnfCallTest() {
66 var called = Query.of("Called", (builder, p1, p2) -> builder.clause(
67 friend.call(p1, p2),
68 friend.call(p2, p1)
69 ));
70 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
71 called.call(p1, v1)
72 ))).getDnf();
73 var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input);
74
75 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
76 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, called.getDnf()).call(p1, v1),
77 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1)
78 ))).getDnf();
79
80 assertThat(actual, structurallyEqualTo(expected));
81 }
82
83 @Test
84 void liftSymbolViewCallTest() {
85 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
86 friendMustView.call(p1, v1)
87 ))).getDnf();
88 var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input);
89
90 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
91 friendMustView.call(p1, v1),
92 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1)
93 ))).getDnf();
94
95 assertThat(actual, structurallyEqualTo(expected));
96 }
97
98 @Test
99 void liftPartialRelationNegativeCallTest() {
100 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
101 not(friend.call(p1, v1)),
102 friend.call(v1, p1)
103 ))).getDnf();
104 var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input);
105
106 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
107 not(ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, v1)),
108 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(v1, p1),
109 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1)
110 ))).getDnf();
111
112 assertThat(actual, structurallyEqualTo(expected));
113 }
114
115 @Test
116 void liftPartialRelationQuantifiedNegativeCallTest() {
117 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
118 person.call(p1),
119 not(friend.call(p1, v1))
120 ))).getDnf();
121 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
122
123 var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause(
124 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, p2),
125 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
126 ));
127 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
128 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1),
129 not(helper.call(p1, v1))
130 ))).getDnf();
131
132 assertThat(actual, structurallyEqualTo(expected));
133 }
134
135 @Test
136 void liftSymbolViewQuantifiedNegativeCallTest() {
137 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
138 person.call(p1),
139 not(friendMustView.call(p1, v1))
140 ))).getDnf();
141 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
142
143 var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause(
144 friendMustView.call(p1, p2),
145 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
146 ));
147 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
148 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1),
149 not(helper.call(p1, v1))
150 ))).getDnf();
151
152 assertThat(actual, structurallyEqualTo(expected));
153 }
154
155 @Test
156 void liftPartialRelationQuantifiedNegativeDiagonalCallTest() {
157 var input = Query.of("Actual", (builder) -> builder.clause((v1) -> List.of(
158 not(friend.call(v1, v1))
159 ))).getDnf();
160 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
161
162 var helper = Query.of("Helper", (builder, p1) -> builder.clause(
163 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, p1),
164 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p1)
165 ));
166 var expected = Query.of("Expected", (builder) -> builder.clause((v1) -> List.of(
167 not(helper.call(v1))
168 ))).getDnf();
169
170 assertThat(actual, structurallyEqualTo(expected));
171 }
172
173 @Test
174 void liftPartialDnfQuantifiedNegativeInputCallTest() {
175 var called = Dnf.of("Called", builder -> {
176 var p1 = builder.parameter("p1", ParameterDirection.IN);
177 var p2 = builder.parameter("p2", ParameterDirection.OUT);
178 builder.clause(
179 friend.call(p1, p2),
180 friend.call(p2, p1)
181 );
182 });
183 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
184 person.call(p1),
185 not(called.call(p1, v1))
186 ))).getDnf();
187 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
188
189 var helper = Dnf.of("Helper", builder -> {
190 var p1 = builder.parameter("p1", ParameterDirection.IN);
191 var p2 = builder.parameter("p2", ParameterDirection.OUT);
192 builder.clause(
193 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, called).call(p1, p2),
194 ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
195 );
196 });
197 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
198 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1),
199 not(helper.call(p1, v1))
200 ))).getDnf();
201
202 assertThat(actual, structurallyEqualTo(expected));
203 }
204
205 @Test
206 void liftPartialRelationTransitiveCallTest() {
207 var input = Query.of("Actual", (builder, p1, p2)-> builder.clause(
208 friend.callTransitive(p1, p2),
209 not(person.call(p2))
210 )).getDnf();
211 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
212
213 var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause(
214 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2),
215 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
216 ));
217 var helper2 = Query.of("Helper2", (builder, p1, p2) -> {
218 builder.clause(
219 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2)
220 );
221 builder.clause((v1) -> List.of(
222 helper.callTransitive(p1, v1),
223 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(v1, p2)
224 ));
225 });
226 var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause(
227 helper2.call(p1, p2),
228 not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(p2))
229 )).getDnf();
230
231 assertThat(actual, structurallyEqualTo(expected));
232 }
233
234 @Test
235 void liftPartialSymbolTransitiveCallTest() {
236 var input = Query.of("Actual", (builder, p1, p2)-> builder.clause(
237 friendMustView.callTransitive(p1, p2),
238 not(person.call(p2))
239 )).getDnf();
240 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
241
242 var endExistsHelper = Query.of("EndExistsHelper", (builder, p1, p2) -> builder.clause(
243 friendMustView.call(p1, p2),
244 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
245 ));
246 var transitiveHelper = Query.of("TransitiveHelper", (builder, p1, p2) -> {
247 builder.clause(
248 friendMustView.call(p1, p2)
249 );
250 builder.clause((v1) -> List.of(
251 endExistsHelper.callTransitive(p1, v1),
252 friendMustView.call(v1, p2)
253 ));
254 });
255 var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause(
256 transitiveHelper.call(p1, p2),
257 not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(p2))
258 )).getDnf();
259
260 assertThat(actual, structurallyEqualTo(expected));
261 }
262
263 @Test
264 void liftPartialRelationTransitiveCallExistsTest() {
265 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
266 friend.callTransitive(p1, v1),
267 not(person.call(v1))
268 ))).getDnf();
269 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
270
271 var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause(
272 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2),
273 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
274 ));
275 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
276 helper.callTransitive(p1, v1),
277 not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(v1))
278 ))).getDnf();
279
280 assertThat(actual, structurallyEqualTo(expected));
281 }
282
283 @Test
284 void liftMultipleTransitiveCallExistsTest() {
285 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
286 friend.callTransitive(p1, v1),
287 friendMustView.callTransitive(p1, v1),
288 not(person.call(v1))
289 ))).getDnf();
290 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
291
292 var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause(
293 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2),
294 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
295 ));
296 var helper2 = Query.of("Helper2", (builder, p1, p2) -> builder.clause(
297 friendMustView.call(p1, p2),
298 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2)
299 ));
300 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
301 helper.callTransitive(p1, v1),
302 helper2.callTransitive(p1, v1),
303 not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(v1))
304 ))).getDnf();
305
306 assertThat(actual, structurallyEqualTo(expected));
307 }
308
309 @Test
310 void liftEquivalentTest() {
311 var input = Query.of("Actual", (builder, p1, p2) -> builder.clause(
312 p1.isEquivalent(p2),
313 person.call(p1)
314 )).getDnf();
315 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
316
317 var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause(
318 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1),
319 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EQUALS_SYMBOL).call(p2, p1)
320 )).getDnf();
321
322 assertThat(actual, structurallyEqualTo(expected));
323 }
324
325 @Test
326 void liftNotEquivalentTest() {
327 var input = Query.of("Actual", (builder, p1, p2) -> builder.clause(
328 not(p1.isEquivalent(p2)),
329 friend.call(p1, p2)
330 )).getDnf();
331 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
332
333 var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause(
334 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2),
335 not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EQUALS_SYMBOL).call(p1, p2))
336 )).getDnf();
337
338 assertThat(actual, structurallyEqualTo(expected));
339 }
340
341 @Test
342 void liftConstantTest() {
343 var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of(
344 v1.isConstant(0),
345 friend.call(v1, p1)
346 ))).getDnf();
347 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
348
349 var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of(
350 v1.isConstant(0),
351 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(v1, p1),
352 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1)
353 ))).getDnf();
354
355 assertThat(actual, structurallyEqualTo(expected));
356 }
357
358 @Test
359 void liftAssignTest() {
360 var input = Query.of("Actual", Integer.class, (builder, p1, output) -> builder
361 .clause(Integer.class, (d1) -> List.of(
362 person.call(p1),
363 ageView.call(p1, d1),
364 output.assign(mul(constant(2), d1))
365 ))).getDnf();
366 var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input);
367
368 var expected = Query.of("Expected", Integer.class, (builder, p1, output) -> builder
369 .clause(Integer.class, (d1) -> List.of(
370 ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1),
371 ageView.call(p1, d1),
372 output.assign(mul(constant(2), d1))
373 ))).getDnf();
374
375 assertThat(actual, structurallyEqualTo(expected));
376 }
377
378 @Test
379 void liftCheckTest() {
380 var input = Query.of("Actual", (builder, p1) -> builder.clause(Integer.class, (d1) -> List.of(
381 person.call(p1),
382 ageView.call(p1, d1),
383 check(greaterEq(d1, constant(21)))
384 ))).getDnf();
385 var actual = sut.lift(Modality.MAY, Concreteness.CANDIDATE, input);
386
387 var expected = Query.of("Expected", (builder, p1) -> builder.clause(Integer.class, (d1) -> List.of(
388 ModalConstraint.of(Modality.MAY, Concreteness.CANDIDATE, person).call(p1),
389 ageView.call(p1, d1),
390 check(greaterEq(d1, constant(21)))
391 ))).getDnf();
392
393 assertThat(actual, structurallyEqualTo(expected));
394 }
395}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java
new file mode 100644
index 00000000..bbfaff84
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java
@@ -0,0 +1,128 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
18import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
19import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy;
20import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
21import tools.refinery.store.representation.TruthValue;
22import tools.refinery.store.representation.cardinality.CardinalityIntervals;
23import tools.refinery.store.tuple.Tuple;
24
25import java.util.Map;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINED_SYMBOL;
30import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINS_SYMBOL;
31import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.COUNT_SYMBOL;
32
33class ContainmentHierarchyTranslatorTest {
34 private final PartialRelation c1 = new PartialRelation("C1", 1);
35 private final PartialRelation c2 = new PartialRelation("C2", 1);
36 private final PartialRelation entry = new PartialRelation("entry", 2);
37
38 private ModelStore store;
39
40 @BeforeEach
41 void beforeEach() {
42
43 var typeHierarchy = TypeHierarchy.builder()
44 .type(CONTAINED_SYMBOL, true)
45 .type(c1)
46 .type(c2, c1, CONTAINED_SYMBOL)
47 .build();
48
49 var containmentHierarchy = Map.of(
50 entry,
51 new ContainmentInfo(c1, UnconstrainedMultiplicity.INSTANCE, c2)
52 );
53
54 store = ModelStore.builder()
55 .with(ViatraModelQueryAdapter.builder())
56 .with(ReasoningAdapter.builder())
57 .with(new MultiObjectTranslator())
58 .with(new TypeHierarchyTranslator(typeHierarchy))
59 .with(new ContainmentHierarchyTranslator(containmentHierarchy))
60 .build();
61 }
62
63 @Test
64 void treeTest() {
65 var modelSeed = ModelSeed.builder(3)
66 .seed(COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE))
67 .seed(CONTAINED_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN))
68 .seed(CONTAINS_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN))
69 .seed(c1, builder -> builder
70 .reducedValue(TruthValue.UNKNOWN)
71 .put(Tuple.of(0), TruthValue.TRUE))
72 .seed(c2, builder -> builder
73 .put(Tuple.of(1), TruthValue.TRUE)
74 .put(Tuple.of(2), TruthValue.TRUE))
75 .seed(entry, builder -> builder
76 .reducedValue(TruthValue.UNKNOWN)
77 .put(Tuple.of(0, 1), TruthValue.TRUE)
78 .put(Tuple.of(0, 2), TruthValue.TRUE))
79 .build();
80
81 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
82 var interpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(Concreteness.PARTIAL,
83 entry);
84
85 assertThat(interpretation.get(Tuple.of(0, 0)), is(TruthValue.FALSE));
86 assertThat(interpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE));
87 assertThat(interpretation.get(Tuple.of(0, 2)), is(TruthValue.TRUE));
88 assertThat(interpretation.get(Tuple.of(1, 0)), is(TruthValue.FALSE));
89 assertThat(interpretation.get(Tuple.of(1, 1)), is(TruthValue.FALSE));
90 assertThat(interpretation.get(Tuple.of(1, 2)), is(TruthValue.FALSE));
91 assertThat(interpretation.get(Tuple.of(2, 0)), is(TruthValue.FALSE));
92 assertThat(interpretation.get(Tuple.of(2, 1)), is(TruthValue.FALSE));
93 assertThat(interpretation.get(Tuple.of(2, 2)), is(TruthValue.FALSE));
94 }
95
96 @Test
97 void loopTest() {
98 var modelSeed = ModelSeed.builder(3)
99 .seed(COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE))
100 .seed(CONTAINED_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN))
101 .seed(CONTAINS_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN))
102 .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN))
103 .seed(c2, builder -> builder
104 .put(Tuple.of(0), TruthValue.TRUE)
105 .put(Tuple.of(1), TruthValue.TRUE)
106 .put(Tuple.of(2), TruthValue.TRUE))
107 .seed(entry, builder -> builder
108 .reducedValue(TruthValue.UNKNOWN)
109 .put(Tuple.of(0, 1), TruthValue.TRUE)
110 .put(Tuple.of(1, 2), TruthValue.TRUE)
111 .put(Tuple.of(2, 0), TruthValue.TRUE))
112 .build();
113
114 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
115 var interpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(Concreteness.PARTIAL,
116 entry);
117
118 assertThat(interpretation.get(Tuple.of(0, 0)), is(TruthValue.FALSE));
119 assertThat(interpretation.get(Tuple.of(0, 1)), is(TruthValue.ERROR));
120 assertThat(interpretation.get(Tuple.of(0, 2)), is(TruthValue.FALSE));
121 assertThat(interpretation.get(Tuple.of(1, 0)), is(TruthValue.FALSE));
122 assertThat(interpretation.get(Tuple.of(1, 1)), is(TruthValue.FALSE));
123 assertThat(interpretation.get(Tuple.of(1, 2)), is(TruthValue.ERROR));
124 assertThat(interpretation.get(Tuple.of(2, 0)), is(TruthValue.ERROR));
125 assertThat(interpretation.get(Tuple.of(2, 1)), is(TruthValue.FALSE));
126 assertThat(interpretation.get(Tuple.of(2, 2)), is(TruthValue.FALSE));
127 }
128}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
new file mode 100644
index 00000000..0f1a1006
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java
@@ -0,0 +1,58 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslationException;
11import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
12import tools.refinery.store.representation.cardinality.CardinalityIntervals;
13
14import static org.junit.jupiter.api.Assertions.assertThrows;
15
16class MetamodelBuilderTest {
17 private final PartialRelation university = new PartialRelation("University", 1);
18 private final PartialRelation course = new PartialRelation("Course", 1);
19 private final PartialRelation courses = new PartialRelation("courses", 2);
20 private final PartialRelation location = new PartialRelation("location", 2);
21
22 @Test
23 void missingOppositeTest() {
24 var builder = Metamodel.builder()
25 .type(university)
26 .type(course)
27 .reference(courses, university, course, location)
28 .reference(location, course, university);
29
30 assertThrows(TranslationException.class, builder::build);
31 }
32
33 @Test
34 void invalidOppositeTypeTest() {
35 var builder = Metamodel.builder()
36 .type(university)
37 .type(course)
38 .reference(courses, university, course, location)
39 .reference(location, course, course, courses);
40
41 assertThrows(TranslationException.class, builder::build);
42 }
43
44 @Test
45 void invalidOppositeMultiplicityTest() {
46 var invalidMultiplicity = new PartialRelation("invalidMultiplicity", 1);
47
48 var builder = Metamodel.builder()
49 .type(university)
50 .type(course)
51 .reference(courses, university, true, course, location)
52 .reference(location, course,
53 ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity),
54 university, courses);
55
56 assertThrows(TranslationException.class, builder::build);
57 }
58}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java
new file mode 100644
index 00000000..eabbdffe
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java
@@ -0,0 +1,152 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
19import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.representation.cardinality.CardinalityIntervals;
22import tools.refinery.store.tuple.Tuple;
23
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.is;
26
27class MetamodelTest {
28 private final PartialRelation person = new PartialRelation("Person", 1);
29 private final PartialRelation student = new PartialRelation("Student", 1);
30 private final PartialRelation teacher = new PartialRelation("Teacher", 1);
31 private final PartialRelation university = new PartialRelation("University", 1);
32 private final PartialRelation course = new PartialRelation("Course", 1);
33 private final PartialRelation courses = new PartialRelation("courses", 2);
34 private final PartialRelation location = new PartialRelation("location", 2);
35 private final PartialRelation lecturer = new PartialRelation("lecturer", 2);
36 private final PartialRelation invalidLecturerCount = new PartialRelation("invalidLecturerCount", 1);
37 private final PartialRelation enrolledStudents = new PartialRelation("enrolledStudents", 2);
38 private final PartialRelation invalidStudentCount = new PartialRelation("invalidStudentCount", 1);
39
40 @Test
41 void metamodelTest() {
42 var metamodel = Metamodel.builder()
43 .type(person, true)
44 .type(student, person)
45 .type(teacher, person)
46 .type(university)
47 .type(course)
48 .reference(courses, university, true, course, location)
49 .reference(location, course, university, courses)
50 .reference(lecturer, course,
51 ConstrainedMultiplicity.of(CardinalityIntervals.ONE, invalidLecturerCount), teacher)
52 .reference(enrolledStudents, course,
53 ConstrainedMultiplicity.of(CardinalityIntervals.SOME, invalidStudentCount), student)
54 .build();
55
56 var seed = ModelSeed.builder(5)
57 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
58 .reducedValue(CardinalityIntervals.ONE)
59 .put(Tuple.of(1), CardinalityIntervals.SET)
60 .put(Tuple.of(4), CardinalityIntervals.SET))
61 .seed(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, builder -> builder
62 .reducedValue(TruthValue.UNKNOWN))
63 .seed(ContainmentHierarchyTranslator.CONTAINS_SYMBOL, builder -> builder
64 .reducedValue(TruthValue.UNKNOWN))
65 .seed(person, builder -> builder.reducedValue(TruthValue.UNKNOWN))
66 .seed(student, builder -> builder.reducedValue(TruthValue.UNKNOWN))
67 .seed(teacher, builder -> builder.reducedValue(TruthValue.UNKNOWN))
68 .seed(university, builder -> builder
69 .reducedValue(TruthValue.UNKNOWN)
70 .put(Tuple.of(0), TruthValue.TRUE))
71 .seed(course, builder -> builder
72 .reducedValue(TruthValue.UNKNOWN)
73 .put(Tuple.of(2), TruthValue.TRUE))
74 .seed(courses, builder -> builder.reducedValue(TruthValue.UNKNOWN))
75 .seed(location, builder -> builder
76 .reducedValue(TruthValue.UNKNOWN)
77 .put(Tuple.of(1, 0), TruthValue.TRUE))
78 .seed(lecturer, builder -> builder
79 .reducedValue(TruthValue.FALSE)
80 .put(Tuple.of(1, 3), TruthValue.TRUE))
81 .seed(enrolledStudents, builder -> builder.reducedValue(TruthValue.UNKNOWN))
82 .build();
83
84 var model = createModel(metamodel, seed);
85 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
86
87 var coursesInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, courses);
88 assertThat(coursesInterpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE));
89 assertThat(coursesInterpretation.get(Tuple.of(0, 2)), is(TruthValue.UNKNOWN));
90 assertThat(coursesInterpretation.get(Tuple.of(0, 3)), is(TruthValue.FALSE));
91
92 var invalidLecturerCountInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL,
93 invalidLecturerCount);
94 assertThat(invalidLecturerCountInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
95 assertThat(invalidLecturerCountInterpretation.get(Tuple.of(2)), is(TruthValue.ERROR));
96
97 var enrolledStudentsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL,
98 enrolledStudents);
99 assertThat(enrolledStudentsInterpretation.get(Tuple.of(1, 3)), is(TruthValue.FALSE));
100 assertThat(enrolledStudentsInterpretation.get(Tuple.of(1, 4)), is(TruthValue.UNKNOWN));
101 }
102
103 @Test
104 void simpleContainmentTest() {
105 var metamodel = Metamodel.builder()
106 .type(university)
107 .type(course)
108 .reference(courses, university, true, course)
109 .build();
110
111
112 var seed = ModelSeed.builder(4)
113 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
114 .reducedValue(CardinalityIntervals.ONE)
115 .put(Tuple.of(0), CardinalityIntervals.SET)
116 .put(Tuple.of(1), CardinalityIntervals.SET))
117 .seed(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, builder -> builder
118 .reducedValue(TruthValue.UNKNOWN))
119 .seed(ContainmentHierarchyTranslator.CONTAINS_SYMBOL, builder -> builder
120 .reducedValue(TruthValue.UNKNOWN))
121 .seed(university, builder -> builder
122 .reducedValue(TruthValue.UNKNOWN)
123 .put(Tuple.of(0), TruthValue.TRUE))
124 .seed(course, builder -> builder
125 .reducedValue(TruthValue.UNKNOWN)
126 .put(Tuple.of(1), TruthValue.TRUE))
127 .seed(courses, builder -> builder
128 .reducedValue(TruthValue.UNKNOWN)
129 .put(Tuple.of(2, 3), TruthValue.TRUE))
130 .build();
131
132 var model = createModel(metamodel, seed);
133 var coursesInterpretation = model.getAdapter(ReasoningAdapter.class)
134 .getPartialInterpretation(Concreteness.PARTIAL, courses);
135
136 assertThat(coursesInterpretation.get(Tuple.of(0, 1)), is(TruthValue.UNKNOWN));
137 assertThat(coursesInterpretation.get(Tuple.of(0, 3)), is(TruthValue.FALSE));
138 assertThat(coursesInterpretation.get(Tuple.of(2, 1)), is(TruthValue.UNKNOWN));
139 assertThat(coursesInterpretation.get(Tuple.of(2, 3)), is(TruthValue.TRUE));
140 }
141
142 private static Model createModel(Metamodel metamodel, ModelSeed seed) {
143 var store = ModelStore.builder()
144 .with(ViatraModelQueryAdapter.builder())
145 .with(ReasoningAdapter.builder())
146 .with(new MultiObjectTranslator())
147 .with(new MetamodelTranslator(metamodel))
148 .build();
149
150 return store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
151 }
152}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/CandidateCountTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/CandidateCountTest.java
new file mode 100644
index 00000000..28391ec7
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/CandidateCountTest.java
@@ -0,0 +1,321 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.ResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
15import tools.refinery.store.reasoning.ReasoningAdapter;
16import tools.refinery.store.reasoning.ReasoningStoreAdapter;
17import tools.refinery.store.reasoning.literal.CountCandidateLowerBoundLiteral;
18import tools.refinery.store.reasoning.literal.CountCandidateUpperBoundLiteral;
19import tools.refinery.store.reasoning.representation.PartialRelation;
20import tools.refinery.store.reasoning.seed.ModelSeed;
21import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24import tools.refinery.store.representation.cardinality.CardinalityIntervals;
25import tools.refinery.store.tuple.Tuple;
26
27import java.util.List;
28
29import static org.hamcrest.MatcherAssert.assertThat;
30import static org.hamcrest.Matchers.is;
31import static tools.refinery.store.query.literal.Literals.not;
32import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
33
34class CandidateCountTest {
35 private static final PartialRelation person = new PartialRelation("Person", 1);
36 private static final PartialRelation friend = new PartialRelation("friend", 2);
37
38 @Test
39 void lowerBoundZeroTest() {
40 var query = Query.of("LowerBound", Integer.class, (builder, p1, p2, output) -> builder.clause(
41 must(person.call(p1)),
42 must(person.call(p2)),
43 new CountCandidateLowerBoundLiteral(output, friend, List.of(p1, p2))
44 ));
45
46 var modelSeed = ModelSeed.builder(2)
47 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
48 .put(Tuple.of(0), CardinalityIntervals.atLeast(3))
49 .put(Tuple.of(1), CardinalityIntervals.atMost(7)))
50 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
51 .seed(friend, builder -> builder
52 .put(Tuple.of(0, 1), TruthValue.TRUE)
53 .put(Tuple.of(1, 0), TruthValue.UNKNOWN)
54 .put(Tuple.of(1, 1), TruthValue.ERROR))
55 .build();
56
57 var resultSet = getResultSet(query, modelSeed);
58 assertThat(resultSet.get(Tuple.of(0, 0)), is(0));
59 assertThat(resultSet.get(Tuple.of(0, 1)), is(1));
60 assertThat(resultSet.get(Tuple.of(1, 0)), is(0));
61 assertThat(resultSet.get(Tuple.of(1, 1)), is(1));
62 }
63
64 @Test
65 void upperBoundZeroTest() {
66 var query = Query.of("UpperBound", Integer.class, (builder, p1, p2, output) -> builder.clause(
67 must(person.call(p1)),
68 must(person.call(p2)),
69 new CountCandidateUpperBoundLiteral(output, friend, List.of(p1, p2))
70 ));
71
72 var modelSeed = ModelSeed.builder(2)
73 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
74 .put(Tuple.of(0), CardinalityIntervals.atLeast(3))
75 .put(Tuple.of(1), CardinalityIntervals.atMost(7)))
76 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
77 .seed(friend, builder -> builder
78 .put(Tuple.of(0, 1), TruthValue.TRUE)
79 .put(Tuple.of(1, 0), TruthValue.UNKNOWN)
80 .put(Tuple.of(1, 1), TruthValue.ERROR))
81 .build();
82
83 var resultSet = getResultSet(query, modelSeed);
84 assertThat(resultSet.get(Tuple.of(0, 0)), is(0));
85 assertThat(resultSet.get(Tuple.of(0, 1)), is(1));
86 assertThat(resultSet.get(Tuple.of(1, 0)), is(0));
87 assertThat(resultSet.get(Tuple.of(1, 1)), is(1));
88 }
89
90 @Test
91 void lowerBoundOneTest() {
92 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(
93 must(person.call(p1)),
94 new CountCandidateLowerBoundLiteral(output, friend, List.of(p1, Variable.of()))
95 ));
96
97 var modelSeed = ModelSeed.builder(4)
98 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
99 .reducedValue(CardinalityIntervals.ONE)
100 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
101 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
102 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
103 .seed(friend, builder -> builder
104 .put(Tuple.of(0, 1), TruthValue.TRUE)
105 .put(Tuple.of(0, 2), TruthValue.TRUE)
106 .put(Tuple.of(0, 3), TruthValue.TRUE)
107 .put(Tuple.of(1, 0), TruthValue.TRUE)
108 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
109 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
110 .put(Tuple.of(2, 0), TruthValue.TRUE)
111 .put(Tuple.of(2, 1), TruthValue.ERROR))
112 .build();
113
114 var resultSet = getResultSet(query, modelSeed);
115 assertThat(resultSet.get(Tuple.of(0)), is(2));
116 assertThat(resultSet.get(Tuple.of(1)), is(1));
117 assertThat(resultSet.get(Tuple.of(2)), is(2));
118 assertThat(resultSet.get(Tuple.of(3)), is(0));
119 }
120
121 @Test
122 void upperBoundOneTest() {
123 var query = Query.of("UpperBound", Integer.class, (builder, p1, output) -> builder.clause(
124 must(person.call(p1)),
125 new CountCandidateUpperBoundLiteral(output, friend, List.of(p1, Variable.of()))
126 ));
127
128 var modelSeed = ModelSeed.builder(4)
129 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
130 .reducedValue(CardinalityIntervals.ONE)
131 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
132 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
133 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
134 .seed(friend, builder -> builder
135 .put(Tuple.of(0, 1), TruthValue.TRUE)
136 .put(Tuple.of(0, 2), TruthValue.TRUE)
137 .put(Tuple.of(0, 3), TruthValue.TRUE)
138 .put(Tuple.of(1, 0), TruthValue.TRUE)
139 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
140 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
141 .put(Tuple.of(2, 0), TruthValue.TRUE)
142 .put(Tuple.of(2, 1), TruthValue.ERROR))
143 .build();
144
145 var resultSet = getResultSet(query, modelSeed);
146 assertThat(resultSet.get(Tuple.of(0)), is(2));
147 assertThat(resultSet.get(Tuple.of(1)), is(1));
148 assertThat(resultSet.get(Tuple.of(2)), is(2));
149 assertThat(resultSet.get(Tuple.of(3)), is(0));
150 }
151
152 @Test
153 void lowerBoundTwoTest() {
154 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
155 friend.call(p1, p2),
156 friend.call(p1, p3),
157 friend.call(p2, p3)
158 ));
159 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(
160 must(person.call(p1)),
161 new CountCandidateLowerBoundLiteral(output, subQuery.getDnf(),
162 List.of(p1, Variable.of(), Variable.of()))
163 ));
164
165 var modelSeed = ModelSeed.builder(4)
166 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
167 .reducedValue(CardinalityIntervals.ONE)
168 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
169 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
170 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
171 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
172 .seed(friend, builder -> builder
173 .put(Tuple.of(0, 1), TruthValue.TRUE)
174 .put(Tuple.of(0, 2), TruthValue.TRUE)
175 .put(Tuple.of(0, 3), TruthValue.TRUE)
176 .put(Tuple.of(1, 0), TruthValue.TRUE)
177 .put(Tuple.of(1, 2), TruthValue.TRUE)
178 .put(Tuple.of(1, 3), TruthValue.TRUE)
179 .put(Tuple.of(2, 0), TruthValue.TRUE)
180 .put(Tuple.of(2, 1), TruthValue.ERROR))
181 .build();
182
183 var resultSet = getResultSet(query, modelSeed);
184 assertThat(resultSet.get(Tuple.of(0)), is(1));
185 assertThat(resultSet.get(Tuple.of(1)), is(1));
186 assertThat(resultSet.get(Tuple.of(2)), is(2));
187 assertThat(resultSet.get(Tuple.of(3)), is(0));
188 }
189
190 @Test
191 void upperBoundTwoTest() {
192 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
193 friend.call(p1, p2),
194 friend.call(p1, p3),
195 friend.call(p2, p3)
196 ));
197 var query = Query.of("UpperBound", Integer.class, (builder, p1, output) -> builder.clause(
198 must(person.call(p1)),
199 new CountCandidateUpperBoundLiteral(output, subQuery.getDnf(),
200 List.of(p1, Variable.of(), Variable.of()))
201 ));
202
203 var modelSeed = ModelSeed.builder(4)
204 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
205 .reducedValue(CardinalityIntervals.ONE)
206 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
207 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
208 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
209 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
210 .seed(friend, builder -> builder
211 .put(Tuple.of(0, 1), TruthValue.TRUE)
212 .put(Tuple.of(0, 2), TruthValue.TRUE)
213 .put(Tuple.of(0, 3), TruthValue.TRUE)
214 .put(Tuple.of(1, 0), TruthValue.TRUE)
215 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
216 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
217 .put(Tuple.of(2, 0), TruthValue.TRUE)
218 .put(Tuple.of(2, 1), TruthValue.ERROR))
219 .build();
220
221 var resultSet = getResultSet(query, modelSeed);
222 assertThat(resultSet.get(Tuple.of(0)), is(0));
223 assertThat(resultSet.get(Tuple.of(1)), is(0));
224 assertThat(resultSet.get(Tuple.of(2)), is(2));
225 assertThat(resultSet.get(Tuple.of(3)), is(0));
226 }
227
228 @Test
229 void lowerBoundDiagonalTest() {
230 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
231 friend.call(p1, p2),
232 friend.call(p1, p3),
233 not(friend.call(p2, p3))
234 ));
235 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(v1 -> List.of(
236 must(person.call(p1)),
237 new CountCandidateLowerBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1))
238 )));
239
240 var modelSeed = ModelSeed.builder(4)
241 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
242 .reducedValue(CardinalityIntervals.ONE)
243 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
244 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
245 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
246 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
247 .seed(friend, builder -> builder
248 .put(Tuple.of(0, 1), TruthValue.TRUE)
249 .put(Tuple.of(0, 2), TruthValue.TRUE)
250 .put(Tuple.of(0, 3), TruthValue.TRUE)
251 .put(Tuple.of(1, 0), TruthValue.TRUE)
252 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
253 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
254 .put(Tuple.of(2, 0), TruthValue.TRUE)
255 .put(Tuple.of(2, 1), TruthValue.ERROR))
256 .build();
257
258 var resultSet = getResultSet(query, modelSeed);
259 assertThat(resultSet.get(Tuple.of(0)), is(2));
260 assertThat(resultSet.get(Tuple.of(1)), is(1));
261 assertThat(resultSet.get(Tuple.of(2)), is(2));
262 assertThat(resultSet.get(Tuple.of(3)), is(0));
263 }
264
265 @Test
266 void upperBoundDiagonalTest() {
267 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
268 friend.call(p1, p2),
269 friend.call(p1, p3),
270 not(friend.call(p2, p3))
271 ));
272 var query = Query.of("UpperBound", Integer.class, (builder, p1, output) -> builder
273 .clause(v1 -> List.of(
274 must(person.call(p1)),
275 new CountCandidateUpperBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1))
276 )));
277
278 var modelSeed = ModelSeed.builder(4)
279 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
280 .reducedValue(CardinalityIntervals.ONE)
281 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
282 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
283 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
284 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
285 .seed(friend, builder -> builder
286 .put(Tuple.of(0, 1), TruthValue.TRUE)
287 .put(Tuple.of(0, 2), TruthValue.TRUE)
288 .put(Tuple.of(0, 3), TruthValue.TRUE)
289 .put(Tuple.of(1, 0), TruthValue.TRUE)
290 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
291 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
292 .put(Tuple.of(2, 0), TruthValue.TRUE)
293 .put(Tuple.of(2, 1), TruthValue.ERROR))
294 .build();
295
296 var resultSet = getResultSet(query, modelSeed);
297 assertThat(resultSet.get(Tuple.of(0)), is(2));
298 assertThat(resultSet.get(Tuple.of(1)), is(1));
299 assertThat(resultSet.get(Tuple.of(2)), is(2));
300 assertThat(resultSet.get(Tuple.of(3)), is(0));
301 }
302
303 private static <T> ResultSet<T> getResultSet(Query<T> query, ModelSeed modelSeed) {
304 var personStorage = Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE);
305 var friendStorage = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE);
306
307 var store = ModelStore.builder()
308 .with(ViatraModelQueryAdapter.builder()
309 .query(query))
310 .with(ReasoningAdapter.builder())
311 .with(new MultiObjectTranslator())
312 .with(PartialRelationTranslator.of(person)
313 .symbol(personStorage))
314 .with(PartialRelationTranslator.of(friend)
315 .symbol(friendStorage))
316 .build();
317
318 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
319 return model.getAdapter(ModelQueryAdapter.class).getResultSet(query);
320 }
321}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java
new file mode 100644
index 00000000..64230cf6
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java
@@ -0,0 +1,321 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.ResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
15import tools.refinery.store.reasoning.ReasoningAdapter;
16import tools.refinery.store.reasoning.ReasoningStoreAdapter;
17import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
18import tools.refinery.store.reasoning.literal.CountUpperBoundLiteral;
19import tools.refinery.store.reasoning.representation.PartialRelation;
20import tools.refinery.store.reasoning.seed.ModelSeed;
21import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24import tools.refinery.store.representation.cardinality.CardinalityIntervals;
25import tools.refinery.store.representation.cardinality.UpperCardinalities;
26import tools.refinery.store.representation.cardinality.UpperCardinality;
27import tools.refinery.store.tuple.Tuple;
28
29import java.util.List;
30
31import static org.hamcrest.MatcherAssert.assertThat;
32import static org.hamcrest.Matchers.is;
33import static tools.refinery.store.query.literal.Literals.not;
34import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
35
36class PartialCountTest {
37 private static final PartialRelation person = new PartialRelation("Person", 1);
38 private static final PartialRelation friend = new PartialRelation("friend", 2);
39
40 @Test
41 void lowerBoundZeroTest() {
42 var query = Query.of("LowerBound", Integer.class, (builder, p1, p2, output) -> builder.clause(
43 must(person.call(p1)),
44 must(person.call(p2)),
45 new CountLowerBoundLiteral(output, friend, List.of(p1, p2))
46 ));
47
48 var modelSeed = ModelSeed.builder(2)
49 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
50 .put(Tuple.of(0), CardinalityIntervals.atLeast(3))
51 .put(Tuple.of(1), CardinalityIntervals.atMost(7)))
52 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
53 .seed(friend, builder -> builder
54 .put(Tuple.of(0, 1), TruthValue.TRUE)
55 .put(Tuple.of(1, 0), TruthValue.UNKNOWN)
56 .put(Tuple.of(1, 1), TruthValue.ERROR))
57 .build();
58
59 var resultSet = getResultSet(query, modelSeed);
60 assertThat(resultSet.get(Tuple.of(0, 0)), is(0));
61 assertThat(resultSet.get(Tuple.of(0, 1)), is(1));
62 assertThat(resultSet.get(Tuple.of(1, 0)), is(0));
63 assertThat(resultSet.get(Tuple.of(1, 1)), is(1));
64 }
65
66 @Test
67 void upperBoundZeroTest() {
68 var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, p2, output) -> builder.clause(
69 must(person.call(p1)),
70 must(person.call(p2)),
71 new CountUpperBoundLiteral(output, friend, List.of(p1, p2))
72 ));
73
74 var modelSeed = ModelSeed.builder(2)
75 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
76 .put(Tuple.of(0), CardinalityIntervals.atLeast(3))
77 .put(Tuple.of(1), CardinalityIntervals.atMost(7)))
78 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
79 .seed(friend, builder -> builder
80 .put(Tuple.of(0, 1), TruthValue.TRUE)
81 .put(Tuple.of(1, 0), TruthValue.UNKNOWN)
82 .put(Tuple.of(1, 1), TruthValue.ERROR))
83 .build();
84
85 var resultSet = getResultSet(query, modelSeed);
86 assertThat(resultSet.get(Tuple.of(0, 0)), is(UpperCardinalities.ZERO));
87 assertThat(resultSet.get(Tuple.of(0, 1)), is(UpperCardinalities.ONE));
88 assertThat(resultSet.get(Tuple.of(1, 0)), is(UpperCardinalities.ONE));
89 assertThat(resultSet.get(Tuple.of(1, 1)), is(UpperCardinalities.ZERO));
90 }
91
92 @Test
93 void lowerBoundOneTest() {
94 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(
95 must(person.call(p1)),
96 new CountLowerBoundLiteral(output, friend, List.of(p1, Variable.of()))
97 ));
98
99 var modelSeed = ModelSeed.builder(4)
100 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
101 .reducedValue(CardinalityIntervals.ONE)
102 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
103 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
104 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
105 .seed(friend, builder -> builder
106 .put(Tuple.of(0, 1), TruthValue.TRUE)
107 .put(Tuple.of(0, 2), TruthValue.TRUE)
108 .put(Tuple.of(0, 3), TruthValue.TRUE)
109 .put(Tuple.of(1, 0), TruthValue.TRUE)
110 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
111 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
112 .put(Tuple.of(2, 0), TruthValue.TRUE)
113 .put(Tuple.of(2, 1), TruthValue.ERROR))
114 .build();
115
116 var resultSet = getResultSet(query, modelSeed);
117 assertThat(resultSet.get(Tuple.of(0)), is(4));
118 assertThat(resultSet.get(Tuple.of(1)), is(1));
119 assertThat(resultSet.get(Tuple.of(2)), is(4));
120 assertThat(resultSet.get(Tuple.of(3)), is(0));
121 }
122
123 @Test
124 void upperBoundOneTest() {
125 var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder.clause(
126 must(person.call(p1)),
127 new CountUpperBoundLiteral(output, friend, List.of(p1, Variable.of()))
128 ));
129
130 var modelSeed = ModelSeed.builder(4)
131 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
132 .reducedValue(CardinalityIntervals.ONE)
133 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
134 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
135 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
136 .seed(friend, builder -> builder
137 .put(Tuple.of(0, 1), TruthValue.TRUE)
138 .put(Tuple.of(0, 2), TruthValue.TRUE)
139 .put(Tuple.of(0, 3), TruthValue.TRUE)
140 .put(Tuple.of(1, 0), TruthValue.TRUE)
141 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
142 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
143 .put(Tuple.of(2, 0), TruthValue.TRUE)
144 .put(Tuple.of(2, 1), TruthValue.ERROR))
145 .build();
146
147 var resultSet = getResultSet(query, modelSeed);
148 assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED));
149 assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(9)));
150 assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.ONE));
151 assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO));
152 }
153
154 @Test
155 void lowerBoundTwoTest() {
156 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
157 friend.call(p1, p2),
158 friend.call(p1, p3),
159 friend.call(p2, p3)
160 ));
161 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(
162 must(person.call(p1)),
163 new CountLowerBoundLiteral(output, subQuery.getDnf(), List.of(p1, Variable.of(), Variable.of()))
164 ));
165
166 var modelSeed = ModelSeed.builder(4)
167 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
168 .reducedValue(CardinalityIntervals.ONE)
169 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
170 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
171 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
172 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
173 .seed(friend, builder -> builder
174 .put(Tuple.of(0, 1), TruthValue.TRUE)
175 .put(Tuple.of(0, 2), TruthValue.TRUE)
176 .put(Tuple.of(0, 3), TruthValue.TRUE)
177 .put(Tuple.of(1, 0), TruthValue.TRUE)
178 .put(Tuple.of(1, 2), TruthValue.TRUE)
179 .put(Tuple.of(1, 3), TruthValue.TRUE)
180 .put(Tuple.of(2, 0), TruthValue.TRUE)
181 .put(Tuple.of(2, 1), TruthValue.ERROR))
182 .build();
183
184 var resultSet = getResultSet(query, modelSeed);
185 assertThat(resultSet.get(Tuple.of(0)), is(3));
186 assertThat(resultSet.get(Tuple.of(1)), is(5));
187 assertThat(resultSet.get(Tuple.of(2)), is(30));
188 assertThat(resultSet.get(Tuple.of(3)), is(0));
189 }
190
191 @Test
192 void upperBoundTwoTest() {
193 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
194 friend.call(p1, p2),
195 friend.call(p1, p3),
196 friend.call(p2, p3)
197 ));
198 var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder.clause(
199 must(person.call(p1)),
200 new CountUpperBoundLiteral(output, subQuery.getDnf(), List.of(p1, Variable.of(), Variable.of()))
201 ));
202
203 var modelSeed = ModelSeed.builder(4)
204 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
205 .reducedValue(CardinalityIntervals.ONE)
206 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
207 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
208 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
209 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
210 .seed(friend, builder -> builder
211 .put(Tuple.of(0, 1), TruthValue.TRUE)
212 .put(Tuple.of(0, 2), TruthValue.TRUE)
213 .put(Tuple.of(0, 3), TruthValue.TRUE)
214 .put(Tuple.of(1, 0), TruthValue.TRUE)
215 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
216 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
217 .put(Tuple.of(2, 0), TruthValue.TRUE)
218 .put(Tuple.of(2, 1), TruthValue.ERROR))
219 .build();
220
221 var resultSet = getResultSet(query, modelSeed);
222 assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED));
223 assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(135)));
224 assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.ZERO));
225 assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO));
226 }
227
228 @Test
229 void lowerBoundDiagonalTest() {
230 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
231 friend.call(p1, p2),
232 friend.call(p1, p3),
233 not(friend.call(p2, p3))
234 ));
235 var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(v1 -> List.of(
236 must(person.call(p1)),
237 new CountLowerBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1))
238 )));
239
240 var modelSeed = ModelSeed.builder(4)
241 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
242 .reducedValue(CardinalityIntervals.ONE)
243 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
244 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
245 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
246 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
247 .seed(friend, builder -> builder
248 .put(Tuple.of(0, 1), TruthValue.TRUE)
249 .put(Tuple.of(0, 2), TruthValue.TRUE)
250 .put(Tuple.of(0, 3), TruthValue.TRUE)
251 .put(Tuple.of(1, 0), TruthValue.TRUE)
252 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
253 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
254 .put(Tuple.of(2, 0), TruthValue.TRUE)
255 .put(Tuple.of(2, 1), TruthValue.ERROR))
256 .build();
257
258 var resultSet = getResultSet(query, modelSeed);
259 assertThat(resultSet.get(Tuple.of(0)), is(4));
260 assertThat(resultSet.get(Tuple.of(1)), is(5));
261 assertThat(resultSet.get(Tuple.of(2)), is(8));
262 assertThat(resultSet.get(Tuple.of(3)), is(0));
263 }
264
265 @Test
266 void upperBoundDiagonalTest() {
267 var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause(
268 friend.call(p1, p2),
269 friend.call(p1, p3),
270 not(friend.call(p2, p3))
271 ));
272 var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder
273 .clause(v1 -> List.of(
274 must(person.call(p1)),
275 new CountUpperBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1))
276 )));
277
278 var modelSeed = ModelSeed.builder(4)
279 .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder
280 .reducedValue(CardinalityIntervals.ONE)
281 .put(Tuple.of(0), CardinalityIntervals.between(5, 9))
282 .put(Tuple.of(1), CardinalityIntervals.atLeast(3))
283 .put(Tuple.of(2), CardinalityIntervals.atMost(7)))
284 .seed(person, builder -> builder.reducedValue(TruthValue.TRUE))
285 .seed(friend, builder -> builder
286 .put(Tuple.of(0, 1), TruthValue.TRUE)
287 .put(Tuple.of(0, 2), TruthValue.TRUE)
288 .put(Tuple.of(0, 3), TruthValue.TRUE)
289 .put(Tuple.of(1, 0), TruthValue.TRUE)
290 .put(Tuple.of(1, 2), TruthValue.UNKNOWN)
291 .put(Tuple.of(1, 3), TruthValue.UNKNOWN)
292 .put(Tuple.of(2, 0), TruthValue.TRUE)
293 .put(Tuple.of(2, 1), TruthValue.ERROR))
294 .build();
295
296 var resultSet = getResultSet(query, modelSeed);
297 assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED));
298 assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(17)));
299 assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.atMost(9)));
300 assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO));
301 }
302
303 private static <T> ResultSet<T> getResultSet(Query<T> query, ModelSeed modelSeed) {
304 var personStorage = Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE);
305 var friendStorage = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE);
306
307 var store = ModelStore.builder()
308 .with(ViatraModelQueryAdapter.builder()
309 .query(query))
310 .with(ReasoningAdapter.builder())
311 .with(new MultiObjectTranslator())
312 .with(PartialRelationTranslator.of(person)
313 .symbol(personStorage))
314 .with(PartialRelationTranslator.of(friend)
315 .symbol(friendStorage))
316 .build();
317
318 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
319 return model.getAdapter(ModelQueryAdapter.class).getResultSet(query);
320 }
321}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java
new file mode 100644
index 00000000..3658d603
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java
@@ -0,0 +1,145 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.representation.TruthValue;
18import tools.refinery.store.tuple.Tuple;
19
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.hamcrest.Matchers.is;
22
23class ConcreteSupertypeTest {
24 private final PartialRelation c1 = new PartialRelation("C1", 1);
25 private final PartialRelation c2 = new PartialRelation("C2", 1);
26
27 private ModelStore store;
28
29 @BeforeEach
30 void beforeEach() {
31 var typeHierarchy = TypeHierarchy.builder()
32 .type(c1)
33 .type(c2, c1)
34 .build();
35
36 store = ModelStore.builder()
37 .with(ViatraModelQueryAdapter.builder())
38 .with(ReasoningAdapter.builder())
39 .with(new TypeHierarchyTranslator(typeHierarchy))
40 .build();
41 }
42
43 @Test
44 void inheritedTypeTrueTest() {
45 var seed = ModelSeed.builder(1)
46 .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN))
47 .seed(c2, builder -> builder
48 .reducedValue(TruthValue.UNKNOWN)
49 .put(Tuple.of(0), TruthValue.TRUE))
50 .build();
51
52 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
53 var adapter = model.getAdapter(ReasoningAdapter.class);
54
55 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE));
56 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.TRUE));
57 }
58
59 @Test
60 void inheritedTypeFalseTest() {
61 var seed = ModelSeed.builder(1)
62 .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN))
63 .seed(c2, builder -> builder
64 .reducedValue(TruthValue.UNKNOWN)
65 .put(Tuple.of(0), TruthValue.FALSE))
66 .build();
67
68 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
69 var adapter = model.getAdapter(ReasoningAdapter.class);
70
71 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)),
72 is(TruthValue.UNKNOWN));
73 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE));
74 }
75
76 @Test
77 void supertypeTrueTest() {
78 var seed = ModelSeed.builder(1)
79 .seed(c1, builder -> builder
80 .reducedValue(TruthValue.UNKNOWN)
81 .put(Tuple.of(0), TruthValue.TRUE))
82 .seed(c2, builder -> builder.reducedValue(TruthValue.UNKNOWN))
83 .build();
84
85 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
86 var adapter = model.getAdapter(ReasoningAdapter.class);
87
88 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE));
89 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)),
90 is(TruthValue.UNKNOWN));
91 }
92
93 @Test
94 void supertypeFalseTest() {
95 var seed = ModelSeed.builder(1)
96 .seed(c1, builder -> builder
97 .reducedValue(TruthValue.UNKNOWN)
98 .put(Tuple.of(0), TruthValue.FALSE))
99 .seed(c2, builder -> builder.reducedValue(TruthValue.UNKNOWN))
100 .build();
101
102 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
103 var adapter = model.getAdapter(ReasoningAdapter.class);
104
105 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.FALSE));
106 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE));
107 }
108
109 @Test
110 void supertypeOnlyTest() {
111 var seed = ModelSeed.builder(1)
112 .seed(c1, builder -> builder
113 .reducedValue(TruthValue.UNKNOWN)
114 .put(Tuple.of(0), TruthValue.TRUE))
115 .seed(c2, builder -> builder
116 .reducedValue(TruthValue.UNKNOWN)
117 .put(Tuple.of(0), TruthValue.FALSE))
118 .build();
119
120 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
121 var adapter = model.getAdapter(ReasoningAdapter.class);
122
123 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE));
124 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE));
125 }
126
127
128 @Test
129 void inheritedTypeErrorTest() {
130 var seed = ModelSeed.builder(1)
131 .seed(c1, builder -> builder
132 .reducedValue(TruthValue.UNKNOWN)
133 .put(Tuple.of(0), TruthValue.FALSE))
134 .seed(c2, builder -> builder
135 .reducedValue(TruthValue.UNKNOWN)
136 .put(Tuple.of(0), TruthValue.TRUE))
137 .build();
138
139 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
140 var adapter = model.getAdapter(ReasoningAdapter.class);
141
142 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.ERROR));
143 assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.ERROR));
144 }
145}
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/TypeAnalysisExampleHierarchyTest.java
index 05a476c6..d9a5477e 100644
--- 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/TypeAnalysisExampleHierarchyTest.java
@@ -5,19 +5,19 @@
5 */ 5 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import org.hamcrest.Matchers;
8import org.junit.jupiter.api.BeforeEach; 9import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test; 10import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation; 11import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.representation.TruthValue; 12import tools.refinery.store.representation.TruthValue;
12 13
13import java.util.LinkedHashMap;
14import java.util.Set; 14import java.util.Set;
15 15
16import static org.hamcrest.MatcherAssert.assertThat; 16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is; 17import static org.hamcrest.Matchers.is;
18import static org.junit.jupiter.api.Assertions.assertAll; 18import static org.junit.jupiter.api.Assertions.assertAll;
19 19
20class TypeAnalyzerExampleHierarchyTest { 20class TypeAnalysisExampleHierarchyTest {
21 private final PartialRelation a1 = new PartialRelation("A1", 1); 21 private final PartialRelation a1 = new PartialRelation("A1", 1);
22 private final PartialRelation a2 = new PartialRelation("A2", 1); 22 private final PartialRelation a2 = new PartialRelation("A2", 1);
23 private final PartialRelation a3 = new PartialRelation("A3", 1); 23 private final PartialRelation a3 = new PartialRelation("A3", 1);
@@ -28,23 +28,23 @@ class TypeAnalyzerExampleHierarchyTest {
28 private final PartialRelation c3 = new PartialRelation("C3", 1); 28 private final PartialRelation c3 = new PartialRelation("C3", 1);
29 private final PartialRelation c4 = new PartialRelation("C4", 1); 29 private final PartialRelation c4 = new PartialRelation("C4", 1);
30 30
31 private TypeAnalyzer sut; 31 private TypeHierarchy sut;
32 private TypeAnalyzerTester tester; 32 private TypeHierarchyTester tester;
33 33
34 @BeforeEach 34 @BeforeEach
35 void beforeEach() { 35 void beforeEach() {
36 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); 36 sut = TypeHierarchy.builder()
37 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); 37 .type(a1, true)
38 typeInfoMap.put(a2, TypeInfo.builder().abstractType().build()); 38 .type(a2, true)
39 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); 39 .type(a3, true)
40 typeInfoMap.put(a4, TypeInfo.builder().abstractType().build()); 40 .type(a4, true)
41 typeInfoMap.put(a5, TypeInfo.builder().abstractType().build()); 41 .type(a5, true)
42 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a1, a4).build()); 42 .type(c1, a1, a4)
43 typeInfoMap.put(c2, TypeInfo.builder().supertypes(a1, a2, a3, a4).build()); 43 .type(c2, a1, a2, a3, a4)
44 typeInfoMap.put(c3, TypeInfo.builder().supertype(a3).build()); 44 .type(c3, a3)
45 typeInfoMap.put(c4, TypeInfo.builder().supertype(a4).build()); 45 .type(c4, a4)
46 sut = new TypeAnalyzer(typeInfoMap); 46 .build();
47 tester = new TypeAnalyzerTester(sut); 47 tester = new TypeHierarchyTester(sut);
48 } 48 }
49 49
50 @Test 50 @Test
@@ -65,16 +65,16 @@ class TypeAnalyzerExampleHierarchyTest {
65 @Test 65 @Test
66 void inferredTypesTest() { 66 void inferredTypesTest() {
67 assertAll( 67 assertAll(
68 () -> assertThat(sut.getUnknownType(), is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))), 68 () -> assertThat(sut.getUnknownType(), Matchers.is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))),
69 () -> assertThat(tester.getInferredType(a1), is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))), 69 () -> assertThat(tester.getInferredType(a1), Matchers.is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))),
70 () -> assertThat(tester.getInferredType(a3), is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))), 70 () -> assertThat(tester.getInferredType(a3), Matchers.is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))),
71 () -> assertThat(tester.getInferredType(a4), is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))), 71 () -> assertThat(tester.getInferredType(a4), Matchers.is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))),
72 () -> assertThat(tester.getInferredType(a5), is(new InferredType(Set.of(a5), Set.of(), null))), 72 () -> assertThat(tester.getInferredType(a5), Matchers.is(new InferredType(Set.of(a5), Set.of(), null))),
73 () -> assertThat(tester.getInferredType(c1), is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), 73 () -> assertThat(tester.getInferredType(c1), Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))),
74 () -> assertThat(tester.getInferredType(c2), 74 () -> assertThat(tester.getInferredType(c2),
75 is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))), 75 Matchers.is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))),
76 () -> assertThat(tester.getInferredType(c3), is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), 76 () -> assertThat(tester.getInferredType(c3), Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
77 () -> assertThat(tester.getInferredType(c4), is(new InferredType(Set.of(a4, c4), Set.of(c4), c4))) 77 () -> assertThat(tester.getInferredType(c4), Matchers.is(new InferredType(Set.of(a4, c4), Set.of(c4), c4)))
78 ); 78 );
79 } 79 }
80 80
@@ -84,8 +84,8 @@ class TypeAnalyzerExampleHierarchyTest {
84 var a3Result = tester.getPreservedType(a3); 84 var a3Result = tester.getPreservedType(a3);
85 var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2); 85 var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2);
86 assertAll( 86 assertAll(
87 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), is(expected)), 87 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), Matchers.is(expected)),
88 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), is(expected)), 88 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), Matchers.is(expected)),
89 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())), 89 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())),
90 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())), 90 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())),
91 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE), 91 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE),
@@ -100,19 +100,19 @@ class TypeAnalyzerExampleHierarchyTest {
100 var a4Result = tester.getPreservedType(a4); 100 var a4Result = tester.getPreservedType(a4);
101 assertAll( 101 assertAll(
102 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE), 102 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE),
103 is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), 103 Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
104 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE), 104 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
105 is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), 105 Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))),
106 () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE), 106 () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE),
107 is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), 107 Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
108 () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE), 108 () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE),
109 is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))), 109 Matchers.is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))),
110 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE), 110 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE),
111 is(new InferredType(Set.of(), Set.of(c3, c4), null))), 111 Matchers.is(new InferredType(Set.of(), Set.of(c3, c4), null))),
112 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE), 112 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE),
113 is(new InferredType(Set.of(), Set.of(c1, c4), null))), 113 Matchers.is(new InferredType(Set.of(), Set.of(c1, c4), null))),
114 () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE), 114 () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE),
115 is(new InferredType(Set.of(), Set.of(c3), null))) 115 Matchers.is(new InferredType(Set.of(), Set.of(c3), null)))
116 ); 116 );
117 } 117 }
118 118
@@ -122,8 +122,8 @@ class TypeAnalyzerExampleHierarchyTest {
122 var a4Result = tester.getPreservedType(a4); 122 var a4Result = tester.getPreservedType(a4);
123 var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null); 123 var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null);
124 assertAll( 124 assertAll(
125 () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), is(expected)), 125 () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), Matchers.is(expected)),
126 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), is(expected)) 126 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), Matchers.is(expected))
127 ); 127 );
128 } 128 }
129 129
@@ -145,9 +145,9 @@ class TypeAnalyzerExampleHierarchyTest {
145 var c3Result = tester.getPreservedType(c3); 145 var c3Result = tester.getPreservedType(c3);
146 assertAll( 146 assertAll(
147 () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE), 147 () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE),
148 is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))), 148 Matchers.is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))),
149 () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), 149 () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE),
150 is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null))) 150 Matchers.is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null)))
151 ); 151 );
152 } 152 }
153 153
@@ -158,13 +158,13 @@ class TypeAnalyzerExampleHierarchyTest {
158 var c1Result = tester.getPreservedType(c1); 158 var c1Result = tester.getPreservedType(c1);
159 assertAll( 159 assertAll(
160 () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE), 160 () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
161 is(new InferredType(Set.of(a1, a4), Set.of(), null))), 161 Matchers.is(new InferredType(Set.of(a1, a4), Set.of(), null))),
162 () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE), 162 () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE),
163 is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), 163 Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))),
164 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE), 164 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE),
165 is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), 165 Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))),
166 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), 166 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
167 is(new InferredType(Set.of(a1, a4), Set.of(), null))) 167 Matchers.is(new InferredType(Set.of(a1, a4), Set.of(), null)))
168 ); 168 );
169 } 169 }
170 170
@@ -174,9 +174,9 @@ class TypeAnalyzerExampleHierarchyTest {
174 var a5Result = tester.getPreservedType(a5); 174 var a5Result = tester.getPreservedType(a5);
175 assertAll( 175 assertAll(
176 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE), 176 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE),
177 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), 177 Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
178 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE), 178 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE),
179 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))) 179 Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null)))
180 ); 180 );
181 } 181 }
182 182
@@ -198,9 +198,9 @@ class TypeAnalyzerExampleHierarchyTest {
198 var a5Result = tester.getPreservedType(a5); 198 var a5Result = tester.getPreservedType(a5);
199 assertAll( 199 assertAll(
200 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR), 200 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR),
201 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), 201 Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
202 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR), 202 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR),
203 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), 203 Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
204 () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR), 204 () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR),
205 is(a5Result.asInferredType())) 205 is(a5Result.asInferredType()))
206 ); 206 );
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
deleted file mode 100644
index d0ef9d57..00000000
--- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.TruthValue;
11
12import java.util.LinkedHashMap;
13import java.util.Set;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.is;
17import static org.junit.jupiter.api.Assertions.assertAll;
18import static org.junit.jupiter.api.Assertions.assertThrows;
19
20class TypeAnalyzerTest {
21 @Test
22 void directSupertypesTest() {
23 var c1 = new PartialRelation("C1", 1);
24 var c2 = new PartialRelation("C2", 1);
25 var c3 = new PartialRelation("C3", 1);
26 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
27 typeInfoMap.put(c1, TypeInfo.builder().supertypes(c2, c3).build());
28 typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build());
29 typeInfoMap.put(c3, TypeInfo.builder().build());
30
31 var sut = new TypeAnalyzer(typeInfoMap);
32 var tester = new TypeAnalyzerTester(sut);
33
34 assertAll(
35 () -> tester.assertConcreteType(c1),
36 () -> tester.assertConcreteType(c2, c1),
37 () -> tester.assertConcreteType(c3, c2)
38 );
39 }
40
41 @Test
42 void typeEliminationAbstractToConcreteTest() {
43 var c1 = new PartialRelation("C1", 1);
44 var c2 = new PartialRelation("C2", 1);
45 var a11 = new PartialRelation("A11", 1);
46 var a12 = new PartialRelation("A12", 1);
47 var a21 = new PartialRelation("A21", 1);
48 var a22 = new PartialRelation("A22", 1);
49 var a3 = new PartialRelation("A3", 1);
50 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
51 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build());
52 typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build());
53 typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build());
54 typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
55 typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
56 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build());
57 typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build());
58
59 var sut = new TypeAnalyzer(typeInfoMap);
60 var tester = new TypeAnalyzerTester(sut);
61
62 assertAll(
63 () -> tester.assertConcreteType(c1),
64 () -> tester.assertConcreteType(c2),
65 () -> tester.assertEliminatedType(a11, c1),
66 () -> tester.assertEliminatedType(a12, c1),
67 () -> tester.assertEliminatedType(a21, c1),
68 () -> tester.assertEliminatedType(a22, c1),
69 () -> tester.assertAbstractType(a3, c1, c2)
70 );
71 }
72
73 @Test
74 void typeEliminationConcreteToAbstractTest() {
75 var c1 = new PartialRelation("C1", 1);
76 var c2 = new PartialRelation("C2", 1);
77 var a11 = new PartialRelation("A11", 1);
78 var a12 = new PartialRelation("A12", 1);
79 var a21 = new PartialRelation("A21", 1);
80 var a22 = new PartialRelation("A22", 1);
81 var a3 = new PartialRelation("A3", 1);
82 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
83 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build());
84 typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build());
85 typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
86 typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
87 typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build());
88 typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build());
89 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build());
90
91 var sut = new TypeAnalyzer(typeInfoMap);
92 var tester = new TypeAnalyzerTester(sut);
93
94 assertAll(
95 () -> tester.assertConcreteType(c1),
96 () -> tester.assertConcreteType(c2),
97 () -> tester.assertEliminatedType(a11, c1),
98 () -> tester.assertEliminatedType(a12, c1),
99 () -> tester.assertEliminatedType(a21, c1),
100 () -> tester.assertEliminatedType(a22, c1),
101 () -> tester.assertAbstractType(a3, c1, c2)
102 );
103 }
104
105 @Test
106 void preserveConcreteTypeTest() {
107 var c1 = new PartialRelation("C1", 1);
108 var a1 = new PartialRelation("A1", 1);
109 var c2 = new PartialRelation("C2", 1);
110 var a2 = new PartialRelation("A2", 1);
111 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
112 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
113 typeInfoMap.put(a1, TypeInfo.builder().abstractType().supertype(c2).build());
114 typeInfoMap.put(c2, TypeInfo.builder().supertype(a2).build());
115 typeInfoMap.put(a2, TypeInfo.builder().abstractType().build());
116
117 var sut = new TypeAnalyzer(typeInfoMap);
118 var tester = new TypeAnalyzerTester(sut);
119
120 assertAll(
121 () -> tester.assertConcreteType(c1),
122 () -> tester.assertEliminatedType(a1, c1),
123 () -> tester.assertConcreteType(c2, c1),
124 () -> tester.assertEliminatedType(a2, c2)
125 );
126 }
127
128 @Test
129 void mostGeneralCurrentTypeTest() {
130 var c1 = new PartialRelation("C1", 1);
131 var c2 = new PartialRelation("C2", 1);
132 var c3 = new PartialRelation("C3", 1);
133 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
134 typeInfoMap.put(c1, TypeInfo.builder().supertype(c3).build());
135 typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build());
136 typeInfoMap.put(c3, TypeInfo.builder().build());
137
138 var sut = new TypeAnalyzer(typeInfoMap);
139 var tester = new TypeAnalyzerTester(sut);
140 var c3Result = tester.getPreservedType(c3);
141
142 var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3);
143 assertAll(
144 () -> assertThat(tester.getInferredType(c3), is(expected)),
145 () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(expected))
146 );
147 }
148
149 @Test
150 void preferFirstConcreteTypeTest() {
151 var a1 = new PartialRelation("A1", 1);
152 var c1 = new PartialRelation("C1", 1);
153 var c2 = new PartialRelation("C2", 1);
154 var c3 = new PartialRelation("C3", 1);
155 var c4 = new PartialRelation("C4", 1);
156 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
157 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
158 typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build());
159 typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build());
160 typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build());
161 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build());
162
163 var sut = new TypeAnalyzer(typeInfoMap);
164 var tester = new TypeAnalyzerTester(sut);
165 var c1Result = tester.getPreservedType(c1);
166 var a1Result = tester.getPreservedType(a1);
167
168 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
169 is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2)));
170 }
171
172 @Test
173 void preferFirstMostGeneralConcreteTypeTest() {
174 var a1 = new PartialRelation("A1", 1);
175 var c1 = new PartialRelation("C1", 1);
176 var c2 = new PartialRelation("C2", 1);
177 var c3 = new PartialRelation("C3", 1);
178 var c4 = new PartialRelation("C4", 1);
179 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
180 typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build());
181 typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build());
182 typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build());
183 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
184 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build());
185
186 var sut = new TypeAnalyzer(typeInfoMap);
187 var tester = new TypeAnalyzerTester(sut);
188 var c1Result = tester.getPreservedType(c1);
189 var a1Result = tester.getPreservedType(a1);
190
191 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
192 is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3)));
193 }
194
195 @Test
196 void circularTypeHierarchyTest() {
197 var c1 = new PartialRelation("C1", 1);
198 var c2 = new PartialRelation("C2", 1);
199 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
200 typeInfoMap.put(c1, TypeInfo.builder().supertype(c2).build());
201 typeInfoMap.put(c2, TypeInfo.builder().supertype(c1).build());
202
203 assertThrows(IllegalArgumentException.class, () -> new TypeAnalyzer(typeInfoMap));
204 }
205}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java
new file mode 100644
index 00000000..cd9df19a
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java
@@ -0,0 +1,186 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.query.ModelQueryAdapter;
13import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.ReasoningStoreAdapter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.seed.ModelSeed;
19import tools.refinery.store.representation.TruthValue;
20import tools.refinery.store.tuple.Tuple;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24
25class TypeHierarchyPartialModelTest {
26 private final PartialRelation person = new PartialRelation("Person", 1);
27 private final PartialRelation member = new PartialRelation("Member", 1);
28 private final PartialRelation student = new PartialRelation("Student", 1);
29 private final PartialRelation teacher = new PartialRelation("Teacher", 1);
30 private final PartialRelation pet = new PartialRelation("Pet", 1);
31
32 private Model model;
33
34 @BeforeEach
35 void beforeEach() {
36 var typeHierarchy = TypeHierarchy.builder()
37 .type(person, true)
38 .type(member, true, person)
39 .type(student, member)
40 .type(teacher, member)
41 .type(pet)
42 .build();
43
44 var store = ModelStore.builder()
45 .with(ViatraModelQueryAdapter.builder())
46 .with(ReasoningAdapter.builder())
47 .with(new TypeHierarchyTranslator(typeHierarchy))
48 .build();
49
50 var seed = ModelSeed.builder(4)
51 .seed(person, builder -> builder
52 .reducedValue(TruthValue.UNKNOWN)
53 .put(Tuple.of(3), TruthValue.FALSE))
54 .seed(member, builder -> builder
55 .reducedValue(TruthValue.UNKNOWN)
56 .put(Tuple.of(1), TruthValue.TRUE)
57 .put(Tuple.of(2), TruthValue.TRUE))
58 .seed(student, builder -> builder
59 .reducedValue(TruthValue.UNKNOWN)
60 .put(Tuple.of(0), TruthValue.TRUE)
61 .put(Tuple.of(2), TruthValue.FALSE))
62 .seed(teacher, builder -> builder.reducedValue(TruthValue.UNKNOWN))
63 .seed(pet, builder -> builder.reducedValue(TruthValue.UNKNOWN))
64 .build();
65 model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed);
66 }
67
68 @Test
69 void initialModelTest() {
70 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
71
72 var personInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, person);
73 assertThat(personInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
74 assertThat(personInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
75 assertThat(personInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
76 assertThat(personInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
77
78 var memberInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, member);
79 assertThat(memberInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
80 assertThat(memberInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
81 assertThat(memberInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
82 assertThat(memberInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
83
84 var studentInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, student);
85 assertThat(studentInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
86 assertThat(studentInterpretation.get(Tuple.of(1)), is(TruthValue.UNKNOWN));
87 assertThat(studentInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE));
88 assertThat(studentInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
89
90 var teacherInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, teacher);
91 assertThat(teacherInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE));
92 assertThat(teacherInterpretation.get(Tuple.of(1)), is(TruthValue.UNKNOWN));
93 assertThat(teacherInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
94 assertThat(teacherInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
95
96 var petInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, pet);
97 assertThat(petInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE));
98 assertThat(petInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
99 assertThat(petInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE));
100 assertThat(petInterpretation.get(Tuple.of(3)), is(TruthValue.UNKNOWN));
101 }
102
103 @Test
104 void initialModelCandidateTest() {
105 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
106
107 var personCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, person);
108 assertThat(personCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
109 assertThat(personCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
110 assertThat(personCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
111 assertThat(personCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
112
113 var memberCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, member);
114 assertThat(memberCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
115 assertThat(memberCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
116 assertThat(memberCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
117 assertThat(memberCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
118
119 var studentCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, student);
120 assertThat(studentCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE));
121 assertThat(studentCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
122 assertThat(studentCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE));
123 assertThat(studentCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
124
125 var teacherCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, teacher);
126 assertThat(teacherCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE));
127 assertThat(teacherCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
128 assertThat(teacherCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE));
129 assertThat(teacherCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
130
131 var petCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, pet);
132 assertThat(petCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE));
133 assertThat(petCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
134 assertThat(petCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE));
135 assertThat(petCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
136 }
137
138 @Test
139 void refinedModelTest() {
140 var reasoningAdapter = model.getAdapter(ReasoningAdapter.class);
141 var studentRefiner = reasoningAdapter.getRefiner(student);
142 studentRefiner.merge(Tuple.of(1), TruthValue.FALSE);
143 studentRefiner.merge(Tuple.of(3), TruthValue.TRUE);
144 model.getAdapter(ModelQueryAdapter.class).flushChanges();
145
146 var personInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, person);
147 assertThat(personInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
148 assertThat(personInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR));
149
150 var personCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, person);
151 assertThat(personCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
152 assertThat(personCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
153
154 var memberInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, member);
155 assertThat(memberInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
156 assertThat(memberInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR));
157
158 var memberCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, member);
159 assertThat(memberCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
160 assertThat(memberCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
161
162 var studentInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, student);
163 assertThat(studentInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
164 assertThat(studentInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR));
165
166 var studentCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, student);
167 assertThat(studentCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
168 assertThat(studentCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
169
170 var teacherInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, teacher);
171 assertThat(teacherInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
172 assertThat(teacherInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
173
174 var teacherCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, teacher);
175 assertThat(teacherCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE));
176 assertThat(teacherCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
177
178 var petInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, pet);
179 assertThat(petInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
180 assertThat(petInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
181
182 var petCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, pet);
183 assertThat(petCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE));
184 assertThat(petCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE));
185 }
186}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java
new file mode 100644
index 00000000..931c62dd
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java
@@ -0,0 +1,224 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.hamcrest.Matchers;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.translator.TranslationException;
12import tools.refinery.store.representation.TruthValue;
13
14import java.util.Set;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.hasEntry;
18import static org.junit.jupiter.api.Assertions.assertAll;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20
21class TypeHierarchyTest {
22 @Test
23 void directSupertypesTest() {
24 var c1 = new PartialRelation("C1", 1);
25 var c2 = new PartialRelation("C2", 1);
26 var c3 = new PartialRelation("C3", 1);
27
28 var sut = TypeHierarchy.builder()
29 .type(c1, c2, c3)
30 .type(c2, c3)
31 .type(c3)
32 .build();
33 var tester = new TypeHierarchyTester(sut);
34
35 assertAll(
36 () -> tester.assertConcreteType(c1),
37 () -> tester.assertConcreteType(c2, c1),
38 () -> tester.assertConcreteType(c3, c2)
39 );
40 }
41
42 @Test
43 void typeEliminationAbstractToConcreteTest() {
44 var c1 = new PartialRelation("C1", 1);
45 var c2 = new PartialRelation("C2", 1);
46 var a11 = new PartialRelation("A11", 1);
47 var a12 = new PartialRelation("A12", 1);
48 var a21 = new PartialRelation("A21", 1);
49 var a22 = new PartialRelation("A22", 1);
50 var a3 = new PartialRelation("A3", 1);
51
52 var sut = TypeHierarchy.builder()
53 .type(a3, true)
54 .type(a21, true, a3)
55 .type(a22, true, a3)
56 .type(a11, true, a21, a22)
57 .type(a12, true, a21, a22)
58 .type(c1, a11, a12)
59 .type(c2, a3)
60 .build();
61 var tester = new TypeHierarchyTester(sut);
62
63 assertAll(
64 () -> tester.assertConcreteType(c1),
65 () -> tester.assertConcreteType(c2),
66 () -> tester.assertEliminatedType(a11, c1),
67 () -> tester.assertEliminatedType(a12, c1),
68 () -> tester.assertEliminatedType(a21, c1),
69 () -> tester.assertEliminatedType(a22, c1),
70 () -> tester.assertAbstractType(a3, c1, c2)
71 );
72 }
73
74 @Test
75 void typeEliminationConcreteToAbstractTest() {
76 var c1 = new PartialRelation("C1", 1);
77 var c2 = new PartialRelation("C2", 1);
78 var a11 = new PartialRelation("A11", 1);
79 var a12 = new PartialRelation("A12", 1);
80 var a21 = new PartialRelation("A21", 1);
81 var a22 = new PartialRelation("A22", 1);
82 var a3 = new PartialRelation("A3", 1);
83
84 var sut = TypeHierarchy.builder()
85 .type(c1, a11, a12)
86 .type(c2, a3)
87 .type(a11, true, a21, a22)
88 .type(a12, true, a21, a22)
89 .type(a21, true, a3)
90 .type(a22, true, a3)
91 .type(a3, true)
92 .build();
93 var tester = new TypeHierarchyTester(sut);
94
95 assertAll(
96 () -> tester.assertConcreteType(c1),
97 () -> tester.assertConcreteType(c2),
98 () -> tester.assertEliminatedType(a11, c1),
99 () -> tester.assertEliminatedType(a12, c1),
100 () -> tester.assertEliminatedType(a21, c1),
101 () -> tester.assertEliminatedType(a22, c1),
102 () -> tester.assertAbstractType(a3, c1, c2)
103 );
104 }
105
106 @Test
107 void preserveConcreteTypeTest() {
108 var c1 = new PartialRelation("C1", 1);
109 var a1 = new PartialRelation("A1", 1);
110 var c2 = new PartialRelation("C2", 1);
111 var a2 = new PartialRelation("A2", 1);
112
113 var sut = TypeHierarchy.builder()
114 .type(c1, a1)
115 .type(a1, true, c2)
116 .type(c2, a2)
117 .type(a2, true)
118 .build();
119 var tester = new TypeHierarchyTester(sut);
120
121 assertAll(
122 () -> tester.assertConcreteType(c1),
123 () -> tester.assertEliminatedType(a1, c1),
124 () -> tester.assertConcreteType(c2, c1),
125 () -> tester.assertEliminatedType(a2, c2)
126 );
127 }
128
129 @Test
130 void mostGeneralCurrentTypeTest() {
131 var c1 = new PartialRelation("C1", 1);
132 var c2 = new PartialRelation("C2", 1);
133 var c3 = new PartialRelation("C3", 1);
134
135 var sut = TypeHierarchy.builder()
136 .type(c1, c3)
137 .type(c2, c3)
138 .type(c3)
139 .build();
140 var tester = new TypeHierarchyTester(sut);
141 var c3Result = tester.getPreservedType(c3);
142
143 var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3);
144 assertAll(
145 () -> assertThat(tester.getInferredType(c3), Matchers.is(expected)),
146 () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), Matchers.is(expected))
147 );
148 }
149
150 @Test
151 void preferFirstConcreteTypeTest() {
152 var a1 = new PartialRelation("A1", 1);
153 var c1 = new PartialRelation("C1", 1);
154 var c2 = new PartialRelation("C2", 1);
155 var c3 = new PartialRelation("C3", 1);
156 var c4 = new PartialRelation("C4", 1);
157
158 var sut = TypeHierarchy.builder()
159 .type(c1, a1)
160 .type(c2, a1)
161 .type(c3, a1)
162 .type(c4, c3)
163 .type(a1, true)
164 .build();
165 var tester = new TypeHierarchyTester(sut);
166 var c1Result = tester.getPreservedType(c1);
167 var a1Result = tester.getPreservedType(a1);
168
169 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
170 Matchers.is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2)));
171 }
172
173 @Test
174 void preferFirstMostGeneralConcreteTypeTest() {
175 var a1 = new PartialRelation("A1", 1);
176 var c1 = new PartialRelation("C1", 1);
177 var c2 = new PartialRelation("C2", 1);
178 var c3 = new PartialRelation("C3", 1);
179 var c4 = new PartialRelation("C4", 1);
180
181 var sut = TypeHierarchy.builder()
182 .type(c4, c3)
183 .type(c3, a1)
184 .type(c2, a1)
185 .type(c1, a1)
186 .type(a1, true)
187 .build();
188 var tester = new TypeHierarchyTester(sut);
189 var c1Result = tester.getPreservedType(c1);
190 var a1Result = tester.getPreservedType(a1);
191
192 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
193 Matchers.is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3)));
194 }
195
196 @Test
197 void circularTypeHierarchyTest() {
198 var c1 = new PartialRelation("C1", 1);
199 var c2 = new PartialRelation("C2", 1);
200 var builder = TypeHierarchy.builder()
201 .type(c1, c2)
202 .type(c2, c1);
203
204 assertThrows(TranslationException.class, builder::build);
205 }
206
207 @Test
208 void chainedEliminationTest() {
209 var a1 = new PartialRelation("A1", 1);
210 var a2 = new PartialRelation("A2", 1);
211 var c1 = new PartialRelation("C1", 1);
212
213 var sut = TypeHierarchy.builder()
214 .type(a1, true)
215 .type(a2, true, a1)
216 .type(c1, a2)
217 .build();
218
219 assertAll(
220 () -> assertThat(sut.getEliminatedTypes(), hasEntry(a1, c1)),
221 () -> assertThat(sut.getEliminatedTypes(), hasEntry(a2, c1))
222 );
223 }
224}
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/TypeHierarchyTester.java
index 2924816e..647bd782 100644
--- 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/TypeHierarchyTester.java
@@ -11,10 +11,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
11import static org.hamcrest.Matchers.*; 11import static org.hamcrest.Matchers.*;
12import static org.hamcrest.Matchers.is; 12import static org.hamcrest.Matchers.is;
13 13
14class TypeAnalyzerTester { 14class TypeHierarchyTester {
15 private final TypeAnalyzer sut; 15 private final TypeHierarchy sut;
16 16
17 public TypeAnalyzerTester(TypeAnalyzer sut) { 17 public TypeHierarchyTester(TypeHierarchy sut) {
18 this.sut = sut; 18 this.sut = sut;
19 } 19 }
20 20
@@ -32,22 +32,21 @@ class TypeAnalyzerTester {
32 32
33 private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous, 33 private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous,
34 PartialRelation... directSubtypes) { 34 PartialRelation... directSubtypes) {
35 var result = sut.getAnalysisResults().get(partialRelation); 35 var result = sut.getPreservedTypes().get(partialRelation);
36 assertThat(result, is(instanceOf(PreservedType.class))); 36 assertThat(result, not(nullValue()));
37 var preservedResult = (PreservedType) result; 37 assertThat(result.isAbstractType(), is(isAbstract));
38 assertThat(preservedResult.isAbstractType(), is(isAbstract)); 38 assertThat(result.isVacuous(), is(isVacuous));
39 assertThat(preservedResult.isVacuous(), is(isVacuous)); 39 assertThat(result.getDirectSubtypes(), hasItems(directSubtypes));
40 assertThat(preservedResult.getDirectSubtypes(), hasItems(directSubtypes));
41 } 40 }
42 41
43 public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) { 42 public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) {
44 var result = sut.getAnalysisResults().get(partialRelation); 43 var result = sut.getEliminatedTypes().get(partialRelation);
45 assertThat(result, is(instanceOf(EliminatedType.class))); 44 assertThat(result, not(nullValue()));
46 assertThat(((EliminatedType) result).replacement(), is(replacement)); 45 assertThat(result, is(replacement));
47 } 46 }
48 47
49 public PreservedType getPreservedType(PartialRelation partialRelation) { 48 public TypeAnalysisResult getPreservedType(PartialRelation partialRelation) {
50 return (PreservedType) sut.getAnalysisResults().get(partialRelation); 49 return sut.getPreservedTypes().get(partialRelation);
51 } 50 }
52 51
53 public InferredType getInferredType(PartialRelation partialRelation) { 52 public InferredType getInferredType(PartialRelation partialRelation) {
diff --git a/subprojects/store/build.gradle.kts b/subprojects/store/build.gradle.kts
index d653f01d..f96922a9 100644
--- a/subprojects/store/build.gradle.kts
+++ b/subprojects/store/build.gradle.kts
@@ -10,6 +10,6 @@ plugins {
10} 10}
11 11
12dependencies { 12dependencies {
13 implementation(libs.eclipseCollections)
14 implementation(libs.eclipseCollections.api) 13 implementation(libs.eclipseCollections.api)
14 runtimeOnly(libs.eclipseCollections)
15} 15}
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
index 0a94d449..5e69e7af 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java
@@ -5,6 +5,9 @@
5 */ 5 */
6package tools.refinery.store.map; 6package tools.refinery.store.map;
7 7
8import java.util.Iterator;
9import java.util.Map;
10
8public final class Cursors { 11public final class Cursors {
9 private Cursors() { 12 private Cursors() {
10 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 13 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
@@ -14,6 +17,18 @@ public final class Cursors {
14 return new Empty<>(); 17 return new Empty<>();
15 } 18 }
16 19
20 public static <K, V> Cursor<K, V> singleton(K key, V value) {
21 return new Singleton<>(key, value);
22 }
23
24 public static <K, V> Cursor<K, V> of(Iterator<Map.Entry<K, V>> iterator) {
25 return new IteratorBasedCursor<>(iterator);
26 }
27
28 public static <K, V> Cursor<K, V> of(Map<K, V> map) {
29 return of(map.entrySet().iterator());
30 }
31
17 private static class Empty<K, V> implements Cursor<K, V> { 32 private static class Empty<K, V> implements Cursor<K, V> {
18 private boolean terminated = false; 33 private boolean terminated = false;
19 34
@@ -38,4 +53,53 @@ public final class Cursors {
38 return false; 53 return false;
39 } 54 }
40 } 55 }
56
57 private static class Singleton<K, V> implements Cursor<K, V> {
58 private State state = State.INITIAL;
59 private final K key;
60 private final V value;
61
62 public Singleton(K key, V value) {
63 this.key = key;
64 this.value = value;
65 }
66
67 @Override
68 public K getKey() {
69 if (state == State.STARTED) {
70 return key;
71 }
72 return null;
73 }
74
75 @Override
76 public V getValue() {
77 if (state == State.STARTED) {
78 return value;
79 }
80 return null;
81 }
82
83 @Override
84 public boolean isTerminated() {
85 return state == State.TERMINATED;
86 }
87
88 @Override
89 public boolean move() {
90 if (state == State.INITIAL) {
91 state = State.STARTED;
92 return true;
93 }
94 state = State.TERMINATED;
95 return false;
96 }
97
98
99 private enum State {
100 INITIAL,
101 STARTED,
102 TERMINATED
103 }
104 }
41} 105}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/IteratorBasedCursor.java b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorBasedCursor.java
new file mode 100644
index 00000000..0ed9b730
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/IteratorBasedCursor.java
@@ -0,0 +1,44 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.map;
7
8import java.util.Iterator;
9import java.util.Map;
10
11public class IteratorBasedCursor<K, V> implements Cursor<K, V> {
12 private final Iterator<Map.Entry<K, V>> iterator;
13 private Map.Entry<K, V> entry;
14 private boolean terminated;
15
16 public IteratorBasedCursor(Iterator<Map.Entry<K, V>> iterator) {
17 this.iterator = iterator;
18 }
19
20 @Override
21 public K getKey() {
22 return entry.getKey();
23 }
24
25 @Override
26 public V getValue() {
27 return entry.getValue();
28 }
29
30 @Override
31 public boolean isTerminated() {
32 return terminated;
33 }
34
35 @Override
36 public boolean move() {
37 if (!terminated && iterator.hasNext()) {
38 entry = iterator.next();
39 return true;
40 }
41 terminated = true;
42 return false;
43 }
44}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
index c19cc817..d530ae87 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/internal/delta/VersionedMapDeltaImpl.java
@@ -5,10 +5,9 @@
5 */ 5 */
6package tools.refinery.store.map.internal.delta; 6package tools.refinery.store.map.internal.delta;
7 7
8import java.util.*;
9
10import tools.refinery.store.map.*; 8import tools.refinery.store.map.*;
11import tools.refinery.store.map.IteratorAsCursor; 9
10import java.util.*;
12 11
13public class VersionedMapDeltaImpl<K, V> implements VersionedMap<K, V> { 12public class VersionedMapDeltaImpl<K, V> implements VersionedMap<K, V> {
14 protected final VersionedMapStoreDeltaImpl<K, V> store; 13 protected final VersionedMapStoreDeltaImpl<K, V> store;
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java
index f906b48a..d650bd06 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/AnyInterpretation.java
@@ -13,4 +13,6 @@ public sealed interface AnyInterpretation permits Interpretation {
13 AnySymbol getSymbol(); 13 AnySymbol getSymbol();
14 14
15 long getSize(); 15 long getSize();
16
17 int getAdjacentSize(int slot, int node);
16} 18}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
index 72f188d3..1b15e4cf 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/Interpretation.java
@@ -19,6 +19,8 @@ public non-sealed interface Interpretation<T> extends AnyInterpretation {
19 19
20 Cursor<Tuple, T> getAll(); 20 Cursor<Tuple, T> getAll();
21 21
22 Cursor<Tuple, T> getAdjacent(int slot, int node);
23
22 T put(Tuple key, T value); 24 T put(Tuple key, T value);
23 25
24 void putAll(Cursor<Tuple, T> cursor); 26 void putAll(Cursor<Tuple, T> cursor);
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java
index 3a4024b5..8f652762 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreBuilder.java
@@ -29,7 +29,9 @@ public interface ModelStoreBuilder {
29 29
30 <T> ModelStoreBuilder symbol(Symbol<T> symbol); 30 <T> ModelStoreBuilder symbol(Symbol<T> symbol);
31 31
32 <T extends ModelAdapterBuilder> ModelStoreBuilder with(T adapterBuilder); 32 ModelStoreBuilder with(ModelAdapterBuilder adapterBuilder);
33
34 ModelStoreBuilder with(ModelStoreConfiguration configuration);
33 35
34 <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType); 36 <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType);
35 37
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreConfiguration.java b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreConfiguration.java
new file mode 100644
index 00000000..e94af5f8
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/ModelStoreConfiguration.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model;
7
8@FunctionalInterface
9public interface ModelStoreConfiguration {
10 void apply(ModelStoreBuilder storeBuilder);
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/BaseIndexer.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/BaseIndexer.java
new file mode 100644
index 00000000..3d7f59d7
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/BaseIndexer.java
@@ -0,0 +1,102 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import org.eclipse.collections.api.factory.Maps;
9import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
10import org.eclipse.collections.api.map.MutableMap;
11import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
12import tools.refinery.store.map.*;
13import tools.refinery.store.tuple.Tuple;
14
15import java.util.Set;
16
17class BaseIndexer<T> {
18 private final MutableIntObjectMap<MutableMap<Tuple, T>>[] maps;
19 private final VersionedMap<Tuple, T> versionedMap;
20
21 public BaseIndexer(int arity, VersionedMap<Tuple, T> map) {
22 if (arity < 2) {
23 throw new IllegalArgumentException("Only arity >= 2 symbols need to be indexed");
24 }
25 // There is no way in Java to create a generic array in a checked way.
26 @SuppressWarnings({"unchecked", "squid:S1905"})
27 var uncheckedMaps = (MutableIntObjectMap<MutableMap<Tuple, T>>[]) new MutableIntObjectMap[arity];
28 maps = uncheckedMaps;
29 for (int i = 0; i < arity; i++) {
30 maps[i] = IntObjectMaps.mutable.empty();
31 }
32 this.versionedMap = map;
33 if (map != null) {
34 var cursor = map.getAll();
35 while (cursor.move()) {
36 put(cursor.getKey(), cursor.getValue());
37 }
38 }
39 }
40
41 public void put(Tuple key, T value) {
42 for (int i = 0; i < maps.length; i++) {
43 var map = maps[i];
44 int element = key.get(i);
45 var adjacentTuples = map.getIfAbsentPut(element, Maps.mutable::empty);
46 adjacentTuples.put(key, value);
47 }
48 }
49
50 public void remove(Tuple key) {
51 for (int i = 0; i < maps.length; i++) {
52 var map = maps[i];
53 int element = key.get(i);
54 var adjacentTuples = map.get(element);
55 if (adjacentTuples == null) {
56 continue;
57 }
58 adjacentTuples.remove(key);
59 if (adjacentTuples.isEmpty()) {
60 map.remove(element);
61 }
62 }
63 }
64
65 private MutableMap<Tuple, T> getAdjacentMap(int slot, int node) {
66 if (slot < 0 || slot >= maps.length) {
67 throw new IllegalArgumentException("Invalid index: " + slot);
68 }
69 var map = maps[slot];
70 return map.get(node);
71 }
72
73 public int getAdjacentSize(int slot, int node) {
74 var adjacentTuples = getAdjacentMap(slot, node);
75 if (adjacentTuples == null) {
76 return 0;
77 }
78 return adjacentTuples.size();
79 }
80
81 public Cursor<Tuple, T> getAdjacent(int slot, int node) {
82 var adjacentTuples = getAdjacentMap(slot, node);
83 if (adjacentTuples == null) {
84 return Cursors.empty();
85 }
86 return new IndexCursor<>(adjacentTuples, versionedMap);
87 }
88
89 private static class IndexCursor<T> extends IteratorBasedCursor<Tuple, T> {
90 private final Set<AnyVersionedMap> dependingMaps;
91
92 public IndexCursor(MutableMap<Tuple, T> map, VersionedMap<Tuple, T> versionedMap) {
93 super(map.entrySet().iterator());
94 dependingMaps = versionedMap == null ? Set.of() : Set.of(versionedMap);
95 }
96
97 @Override
98 public Set<AnyVersionedMap> getDependingMaps() {
99 return dependingMaps;
100 }
101 }
102}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
new file mode 100644
index 00000000..0be16f77
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/IndexedVersionedInterpretation.java
@@ -0,0 +1,48 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Objects;
14
15class IndexedVersionedInterpretation<T> extends VersionedInterpretation<T> {
16 private final BaseIndexer<T> indexer;
17
18 public IndexedVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
19 super(model, symbol, map);
20 indexer = new BaseIndexer<>(symbol.arity(), map);
21 }
22
23 @Override
24 public Cursor<Tuple, T> getAdjacent(int slot, int node) {
25 return indexer.getAdjacent(slot, node);
26 }
27
28 @Override
29 public int getAdjacentSize(int slot, int node) {
30 return indexer.getAdjacentSize(slot, node);
31 }
32
33 @Override
34 protected boolean shouldNotifyRestoreListeners() {
35 // Always call the {@code valueChanged} method to update the index.
36 return true;
37 }
38
39 @Override
40 protected void valueChanged(Tuple key, T fromValue, T toValue, boolean restoring) {
41 if (Objects.equals(toValue, getSymbol().defaultValue())) {
42 indexer.remove(key);
43 } else {
44 indexer.put(key, toValue);
45 }
46 super.valueChanged(key, fromValue, toValue, restoring);
47 }
48}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
index 2bd187a8..2dde7a4c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelStoreBuilderImpl.java
@@ -12,6 +12,7 @@ import tools.refinery.store.map.VersionedMapStoreFactory;
12import tools.refinery.store.map.VersionedMapStoreFactoryBuilder; 12import tools.refinery.store.map.VersionedMapStoreFactoryBuilder;
13import tools.refinery.store.model.ModelStore; 13import tools.refinery.store.model.ModelStore;
14import tools.refinery.store.model.ModelStoreBuilder; 14import tools.refinery.store.model.ModelStoreBuilder;
15import tools.refinery.store.model.ModelStoreConfiguration;
15import tools.refinery.store.representation.AnySymbol; 16import tools.refinery.store.representation.AnySymbol;
16import tools.refinery.store.representation.Symbol; 17import tools.refinery.store.representation.Symbol;
17import tools.refinery.store.tuple.Tuple; 18import tools.refinery.store.tuple.Tuple;
@@ -26,7 +27,8 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
26 @Override 27 @Override
27 public <T> ModelStoreBuilder symbol(Symbol<T> symbol) { 28 public <T> ModelStoreBuilder symbol(Symbol<T> symbol) {
28 if (!allSymbols.add(symbol)) { 29 if (!allSymbols.add(symbol)) {
29 throw new IllegalArgumentException("Symbol %s already added".formatted(symbol)); 30 // No need to add symbol twice.
31 return this;
30 } 32 }
31 var equivalenceClass = new SymbolEquivalenceClass<>(symbol); 33 var equivalenceClass = new SymbolEquivalenceClass<>(symbol);
32 var symbolsInEquivalenceClass = equivalenceClasses.computeIfAbsent(equivalenceClass, 34 var symbolsInEquivalenceClass = equivalenceClasses.computeIfAbsent(equivalenceClass,
@@ -36,7 +38,7 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
36 } 38 }
37 39
38 @Override 40 @Override
39 public <T extends ModelAdapterBuilder> ModelStoreBuilder with(T adapterBuilder) { 41 public ModelStoreBuilder with(ModelAdapterBuilder adapterBuilder) {
40 for (var existingAdapter : adapters) { 42 for (var existingAdapter : adapters) {
41 if (existingAdapter.getClass().equals(adapterBuilder.getClass())) { 43 if (existingAdapter.getClass().equals(adapterBuilder.getClass())) {
42 throw new IllegalArgumentException("%s adapter was already configured for store builder" 44 throw new IllegalArgumentException("%s adapter was already configured for store builder"
@@ -48,6 +50,12 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
48 } 50 }
49 51
50 @Override 52 @Override
53 public ModelStoreBuilder with(ModelStoreConfiguration configuration) {
54 configuration.apply(this);
55 return this;
56 }
57
58 @Override
51 public <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType) { 59 public <T extends ModelAdapterBuilder> Optional<T> tryGetAdapter(Class<? extends T> adapterType) {
52 return AdapterUtils.tryGetAdapter(adapters, adapterType); 60 return AdapterUtils.tryGetAdapter(adapters, adapterType);
53 } 61 }
@@ -59,6 +67,7 @@ public class ModelStoreBuilderImpl implements ModelStoreBuilder {
59 67
60 @Override 68 @Override
61 public ModelStore build() { 69 public ModelStore build() {
70 // First configure adapters and let them register any symbols we don't know about yet.
62 for (int i = adapters.size() - 1; i >= 0; i--) { 71 for (int i = adapters.size() - 1; i >= 0; i--) {
63 adapters.get(i).configure(this); 72 adapters.get(i).configure(this);
64 } 73 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
new file mode 100644
index 00000000..4a8e6752
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/NullaryVersionedInterpretation.java
@@ -0,0 +1,27 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.VersionedMap;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13class NullaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
14 public NullaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
15 super(model, symbol, map);
16 }
17
18 @Override
19 public Cursor<Tuple, T> getAdjacent(int slot, int node) {
20 throw new IllegalArgumentException("Invalid index: " + slot);
21 }
22
23 @Override
24 public int getAdjacentSize(int slot, int node) {
25 throw new IllegalArgumentException("Invalid index: " + slot);
26 }
27}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
new file mode 100644
index 00000000..75946680
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/UnaryVersionedInterpretation.java
@@ -0,0 +1,48 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.model.internal;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.map.VersionedMap;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16class UnaryVersionedInterpretation<T> extends VersionedInterpretation<T> {
17 public UnaryVersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
18 super(model, symbol, map);
19 }
20
21 private void validateSlot(int slot) {
22 if (slot != 0) {
23 throw new IllegalArgumentException("Invalid index: " + slot);
24 }
25 }
26
27 @Override
28 public Cursor<Tuple, T> getAdjacent(int slot, int node) {
29 validateSlot(slot);
30 var key = Tuple.of(node);
31 var value = get(key);
32 if (Objects.equals(value, getSymbol().defaultValue())) {
33 return Cursors.empty();
34 }
35 return Cursors.singleton(key, value);
36 }
37
38 @Override
39 public int getAdjacentSize(int slot, int node) {
40 validateSlot(slot);
41 var key = Tuple.of(node);
42 var value = get(key);
43 if (Objects.equals(value, getSymbol().defaultValue())) {
44 return 0;
45 }
46 return 1;
47 }
48}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
index 76e3baea..71df3962 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/model/internal/VersionedInterpretation.java
@@ -16,14 +16,14 @@ import tools.refinery.store.tuple.Tuple;
16import java.util.ArrayList; 16import java.util.ArrayList;
17import java.util.List; 17import java.util.List;
18 18
19public class VersionedInterpretation<T> implements Interpretation<T> { 19public abstract class VersionedInterpretation<T> implements Interpretation<T> {
20 private final ModelImpl model; 20 private final ModelImpl model;
21 private final Symbol<T> symbol; 21 private final Symbol<T> symbol;
22 private final VersionedMap<Tuple, T> map; 22 private final VersionedMap<Tuple, T> map;
23 private final List<InterpretationListener<T>> listeners = new ArrayList<>(); 23 private final List<InterpretationListener<T>> listeners = new ArrayList<>();
24 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>(); 24 private final List<InterpretationListener<T>> restoreListeners = new ArrayList<>();
25 25
26 private VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) { 26 protected VersionedInterpretation(ModelImpl model, Symbol<T> symbol, VersionedMap<Tuple, T> map) {
27 this.model = model; 27 this.model = model;
28 this.symbol = symbol; 28 this.symbol = symbol;
29 this.map = map; 29 this.map = map;
@@ -50,6 +50,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
50 .formatted(symbol, symbol.arity())); 50 .formatted(symbol, symbol.arity()));
51 } 51 }
52 } 52 }
53
53 @Override 54 @Override
54 public T get(Tuple key) { 55 public T get(Tuple key) {
55 checkKey(key); 56 checkKey(key);
@@ -61,7 +62,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
61 return map.getAll(); 62 return map.getAll();
62 } 63 }
63 64
64 private void notifyListeners(Tuple key, T fromValue, T toValue, boolean restoring) { 65 protected void valueChanged(Tuple key, T fromValue, T toValue, boolean restoring) {
65 var listenerList = restoring ? restoreListeners : listeners; 66 var listenerList = restoring ? restoreListeners : listeners;
66 int listenerCount = listenerList.size(); 67 int listenerCount = listenerList.size();
67 // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead. 68 // Use a for loop instead of a for-each loop to avoid <code>Iterator</code> allocation overhead.
@@ -76,7 +77,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
76 checkKey(key); 77 checkKey(key);
77 model.markAsChanged(); 78 model.markAsChanged();
78 var oldValue = map.put(key, value); 79 var oldValue = map.put(key, value);
79 notifyListeners(key, oldValue, value, false); 80 valueChanged(key, oldValue, value, false);
80 return oldValue; 81 return oldValue;
81 } 82 }
82 83
@@ -114,11 +115,16 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
114 Version commit() { 115 Version commit() {
115 return map.commit(); 116 return map.commit();
116 } 117 }
117 void restore(Version state) { 118
118 if (!restoreListeners.isEmpty()) { 119 protected boolean shouldNotifyRestoreListeners() {
120 return !restoreListeners.isEmpty();
121 }
122
123 public void restore(Version state) {
124 if (shouldNotifyRestoreListeners()) {
119 var diffCursor = getDiffCursor(state); 125 var diffCursor = getDiffCursor(state);
120 while (diffCursor.move()) { 126 while (diffCursor.move()) {
121 notifyListeners(diffCursor.getKey(), diffCursor.getFromValue(), diffCursor.getToValue(), true); 127 valueChanged(diffCursor.getKey(), diffCursor.getFromValue(), diffCursor.getToValue(), true);
122 } 128 }
123 } 129 }
124 map.restore(state); 130 map.restore(state);
@@ -142,7 +148,7 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
142 @SuppressWarnings("unchecked") 148 @SuppressWarnings("unchecked")
143 var typedSymbol = (Symbol<T>) symbol; 149 var typedSymbol = (Symbol<T>) symbol;
144 var map = store.createMap(); 150 var map = store.createMap();
145 return new VersionedInterpretation<>(model, typedSymbol, map); 151 return of(model, typedSymbol, map);
146 } 152 }
147 153
148 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store, 154 static <T> VersionedInterpretation<T> of(ModelImpl model, AnySymbol symbol, VersionedMapStore<Tuple, T> store,
@@ -150,6 +156,15 @@ public class VersionedInterpretation<T> implements Interpretation<T> {
150 @SuppressWarnings("unchecked") 156 @SuppressWarnings("unchecked")
151 var typedSymbol = (Symbol<T>) symbol; 157 var typedSymbol = (Symbol<T>) symbol;
152 var map = store.createMap(state); 158 var map = store.createMap(state);
153 return new VersionedInterpretation<>(model, typedSymbol, map); 159 return of(model, typedSymbol, map);
160 }
161
162 private static <T> VersionedInterpretation<T> of(ModelImpl model, Symbol<T> typedSymbol,
163 VersionedMap<Tuple, T> map) {
164 return switch (typedSymbol.arity()) {
165 case 0 -> new NullaryVersionedInterpretation<>(model, typedSymbol, map);
166 case 1 -> new UnaryVersionedInterpretation<>(model, typedSymbol, map);
167 default -> new IndexedVersionedInterpretation<>(model, typedSymbol, map);
168 };
154 } 169 }
155} 170}
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
index 52c740e8..dfdb43bd 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java
@@ -5,6 +5,7 @@
5 */ 5 */
6package tools.refinery.store.representation; 6package tools.refinery.store.representation;
7 7
8import java.util.Objects;
8import java.util.Optional; 9import java.util.Optional;
9 10
10public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain { 11public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain {
@@ -22,7 +23,9 @@ public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain {
22 return toConcrete(abstractValue).isPresent(); 23 return toConcrete(abstractValue).isPresent();
23 } 24 }
24 25
25 boolean isRefinement(A originalValue, A refinedValue); 26 default boolean isRefinement(A originalValue, A refinedValue) {
27 return Objects.equals(commonRefinement(originalValue, refinedValue), refinedValue);
28 }
26 29
27 A commonRefinement(A leftValue, A rightValue); 30 A commonRefinement(A leftValue, A rightValue);
28 31
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java
index 40baf9a5..f81ee9a4 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValue.java
@@ -36,6 +36,10 @@ public enum TruthValue {
36 return this != UNKNOWN; 36 return this != UNKNOWN;
37 } 37 }
38 38
39 public boolean isConcrete() {
40 return this == TRUE || this == FALSE;
41 }
42
39 public boolean must() { 43 public boolean must() {
40 return this == TRUE || this == ERROR; 44 return this == TRUE || this == ERROR;
41 } 45 }
@@ -55,9 +59,18 @@ public enum TruthValue {
55 public TruthValue merge(TruthValue other) { 59 public TruthValue merge(TruthValue other) {
56 return switch (this) { 60 return switch (this) {
57 case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR; 61 case TRUE -> other == UNKNOWN || other == TRUE ? TRUE : ERROR;
58 case FALSE -> other == TruthValue.UNKNOWN || other == TruthValue.FALSE ? FALSE : ERROR; 62 case FALSE -> other == UNKNOWN || other == FALSE ? FALSE : ERROR;
59 case UNKNOWN -> other; 63 case UNKNOWN -> other;
60 default -> ERROR; 64 case ERROR -> ERROR;
65 };
66 }
67
68 public TruthValue join(TruthValue other) {
69 return switch (this) {
70 case TRUE -> other == ERROR || other == TRUE ? TRUE : UNKNOWN;
71 case FALSE -> other == ERROR || other == FALSE ? FALSE : UNKNOWN;
72 case UNKNOWN -> UNKNOWN;
73 case ERROR -> other;
61 }; 74 };
62 } 75 }
63} 76}
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
index 89f8dd19..61696dca 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java
@@ -7,6 +7,8 @@ package tools.refinery.store.representation;
7 7
8import java.util.Optional; 8import java.util.Optional;
9 9
10// Singleton pattern, because there is only one domain for truth values.
11@SuppressWarnings("squid:S6548")
10public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolean> { 12public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolean> {
11 public static final TruthValueDomain INSTANCE = new TruthValueDomain(); 13 public static final TruthValueDomain INSTANCE = new TruthValueDomain();
12 14
@@ -15,51 +17,50 @@ public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolea
15 17
16 @Override 18 @Override
17 public Class<TruthValue> abstractType() { 19 public Class<TruthValue> abstractType() {
18 return null; 20 return TruthValue.class;
19 } 21 }
20 22
21 @Override 23 @Override
22 public Class<Boolean> concreteType() { 24 public Class<Boolean> concreteType() {
23 return null; 25 return Boolean.class;
24 } 26 }
25 27
26 @Override 28 @Override
27 public TruthValue toAbstract(Boolean concreteValue) { 29 public TruthValue toAbstract(Boolean concreteValue) {
28 return null; 30 return TruthValue.toTruthValue(concreteValue);
29 } 31 }
30 32
31 @Override 33 @Override
32 public Optional<Boolean> toConcrete(TruthValue abstractValue) { 34 public Optional<Boolean> toConcrete(TruthValue abstractValue) {
33 return Optional.empty(); 35 return switch (abstractValue) {
36 case TRUE -> Optional.of(true);
37 case FALSE -> Optional.of(false);
38 default -> Optional.empty();
39 };
34 } 40 }
35 41
36 @Override 42 @Override
37 public boolean isConcrete(TruthValue abstractValue) { 43 public boolean isConcrete(TruthValue abstractValue) {
38 return AbstractDomain.super.isConcrete(abstractValue); 44 return abstractValue.isConcrete();
39 }
40
41 @Override
42 public boolean isRefinement(TruthValue originalValue, TruthValue refinedValue) {
43 return false;
44 } 45 }
45 46
46 @Override 47 @Override
47 public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) { 48 public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) {
48 return null; 49 return leftValue.merge(rightValue);
49 } 50 }
50 51
51 @Override 52 @Override
52 public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) { 53 public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) {
53 return null; 54 return leftValue.join(rightValue);
54 } 55 }
55 56
56 @Override 57 @Override
57 public TruthValue unknown() { 58 public TruthValue unknown() {
58 return null; 59 return TruthValue.UNKNOWN;
59 } 60 }
60 61
61 @Override 62 @Override
62 public boolean isError(TruthValue abstractValue) { 63 public boolean isError(TruthValue abstractValue) {
63 return false; 64 return !abstractValue.isConsistent();
64 } 65 }
65} 66}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityDomain.java
new file mode 100644
index 00000000..7ae2d935
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityDomain.java
@@ -0,0 +1,68 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.representation.cardinality;
7
8import tools.refinery.store.representation.AbstractDomain;
9
10import java.util.Optional;
11
12// Singleton pattern, because there is only one domain for truth values.
13@SuppressWarnings("squid:S6548")
14public class CardinalityDomain implements AbstractDomain<CardinalityInterval, Integer> {
15 public static final CardinalityDomain INSTANCE = new CardinalityDomain();
16
17 private CardinalityDomain() {
18 }
19
20 @Override
21 public Class<CardinalityInterval> abstractType() {
22 return CardinalityInterval.class;
23 }
24
25 @Override
26 public Class<Integer> concreteType() {
27 return Integer.class;
28 }
29
30 @Override
31 public CardinalityInterval toAbstract(Integer concreteValue) {
32 return CardinalityIntervals.exactly(concreteValue);
33 }
34
35 @Override
36 public Optional<Integer> toConcrete(CardinalityInterval abstractValue) {
37 return isConcrete(abstractValue) ? Optional.of(abstractValue.lowerBound()) : Optional.empty();
38 }
39
40 @Override
41 public boolean isConcrete(CardinalityInterval abstractValue) {
42 if (!(abstractValue instanceof NonEmptyCardinalityInterval nonEmptyValue) ||
43 !((nonEmptyValue.upperBound()) instanceof FiniteUpperCardinality finiteUpperCardinality)) {
44 return false;
45 }
46 return nonEmptyValue.lowerBound() == finiteUpperCardinality.finiteUpperBound();
47 }
48
49 @Override
50 public CardinalityInterval commonRefinement(CardinalityInterval leftValue, CardinalityInterval rightValue) {
51 return leftValue.meet(rightValue);
52 }
53
54 @Override
55 public CardinalityInterval commonAncestor(CardinalityInterval leftValue, CardinalityInterval rightValue) {
56 return leftValue.join(rightValue);
57 }
58
59 @Override
60 public CardinalityInterval unknown() {
61 return CardinalityIntervals.SET;
62 }
63
64 @Override
65 public boolean isError(CardinalityInterval abstractValue) {
66 return abstractValue.isEmpty();
67 }
68}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java
index 704ca2fc..b20c685a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityInterval.java
@@ -18,6 +18,8 @@ public sealed interface CardinalityInterval permits NonEmptyCardinalityInterval,
18 18
19 CardinalityInterval add(CardinalityInterval other); 19 CardinalityInterval add(CardinalityInterval other);
20 20
21 CardinalityInterval take(int count);
22
21 CardinalityInterval multiply(CardinalityInterval other); 23 CardinalityInterval multiply(CardinalityInterval other);
22 24
23 CardinalityInterval meet(CardinalityInterval other); 25 CardinalityInterval meet(CardinalityInterval other);
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java
index ad16a3e8..855fd248 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/CardinalityIntervals.java
@@ -30,7 +30,7 @@ public final class CardinalityIntervals {
30 } 30 }
31 31
32 public static CardinalityInterval between(int lowerBound, int upperBound) { 32 public static CardinalityInterval between(int lowerBound, int upperBound) {
33 return between(lowerBound, UpperCardinalities.valueOf(upperBound)); 33 return between(lowerBound, UpperCardinalities.atMost(upperBound));
34 } 34 }
35 35
36 public static CardinalityInterval atMost(UpperCardinality upperBound) { 36 public static CardinalityInterval atMost(UpperCardinality upperBound) {
@@ -38,7 +38,7 @@ public final class CardinalityIntervals {
38 } 38 }
39 39
40 public static CardinalityInterval atMost(int upperBound) { 40 public static CardinalityInterval atMost(int upperBound) {
41 return atMost(UpperCardinalities.valueOf(upperBound)); 41 return atMost(UpperCardinalities.atMost(upperBound));
42 } 42 }
43 43
44 public static CardinalityInterval atLeast(int lowerBound) { 44 public static CardinalityInterval atLeast(int lowerBound) {
@@ -46,6 +46,6 @@ public final class CardinalityIntervals {
46 } 46 }
47 47
48 public static CardinalityInterval exactly(int lowerBound) { 48 public static CardinalityInterval exactly(int lowerBound) {
49 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.valueOf(lowerBound)); 49 return new NonEmptyCardinalityInterval(lowerBound, UpperCardinalities.atMost(lowerBound));
50 } 50 }
51} 51}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java
index 49911c29..9e371e21 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/EmptyCardinalityInterval.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
7 7
8// Singleton implementation, because there is only a single empty interval.
9@SuppressWarnings("squid:S6548")
8public final class EmptyCardinalityInterval implements CardinalityInterval { 10public final class EmptyCardinalityInterval implements CardinalityInterval {
9 static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval(); 11 static final EmptyCardinalityInterval INSTANCE = new EmptyCardinalityInterval();
10 12
@@ -43,6 +45,11 @@ public final class EmptyCardinalityInterval implements CardinalityInterval {
43 } 45 }
44 46
45 @Override 47 @Override
48 public CardinalityInterval take(int count) {
49 return this;
50 }
51
52 @Override
46 public CardinalityInterval multiply(CardinalityInterval other) { 53 public CardinalityInterval multiply(CardinalityInterval other) {
47 return this; 54 return this;
48 } 55 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java
index 82afdbbc..088e3925 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/FiniteUpperCardinality.java
@@ -6,6 +6,7 @@
6package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
7 7
8import org.jetbrains.annotations.NotNull; 8import org.jetbrains.annotations.NotNull;
9import org.jetbrains.annotations.Nullable;
9 10
10import java.util.function.IntBinaryOperator; 11import java.util.function.IntBinaryOperator;
11 12
@@ -22,6 +23,15 @@ public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardi
22 } 23 }
23 24
24 @Override 25 @Override
26 @Nullable
27 public UpperCardinality take(int count) {
28 if (finiteUpperBound < count) {
29 return null;
30 }
31 return new FiniteUpperCardinality(finiteUpperBound - count);
32 }
33
34 @Override
25 public UpperCardinality multiply(UpperCardinality other) { 35 public UpperCardinality multiply(UpperCardinality other) {
26 return lift(other, (a, b) -> a * b); 36 return lift(other, (a, b) -> a * b);
27 } 37 }
@@ -49,7 +59,7 @@ public record FiniteUpperCardinality(int finiteUpperBound) implements UpperCardi
49 59
50 private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) { 60 private UpperCardinality lift(@NotNull UpperCardinality other, IntBinaryOperator operator) {
51 if (other instanceof FiniteUpperCardinality finiteUpperCardinality) { 61 if (other instanceof FiniteUpperCardinality finiteUpperCardinality) {
52 return UpperCardinalities.valueOf(operator.applyAsInt(finiteUpperBound, 62 return UpperCardinalities.atMost(operator.applyAsInt(finiteUpperBound,
53 finiteUpperCardinality.finiteUpperBound)); 63 finiteUpperCardinality.finiteUpperBound));
54 } 64 }
55 if (other instanceof UnboundedUpperCardinality) { 65 if (other instanceof UnboundedUpperCardinality) {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
index 38bd53bf..bfaeea25 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/NonEmptyCardinalityInterval.java
@@ -53,6 +53,16 @@ public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upper
53 return lift(other, Math::min, UpperCardinality::max, this); 53 return lift(other, Math::min, UpperCardinality::max, this);
54 } 54 }
55 55
56 @Override
57 public CardinalityInterval take(int count) {
58 int newLowerBound = Math.max(lowerBound - count, 0);
59 var newUpperBound = upperBound.take(count);
60 if (newUpperBound == null) {
61 return CardinalityIntervals.ERROR;
62 }
63 return CardinalityIntervals.between(newLowerBound, newUpperBound);
64 }
65
56 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator, 66 private CardinalityInterval lift(CardinalityInterval other, IntBinaryOperator lowerOperator,
57 BinaryOperator<UpperCardinality> upperOperator, 67 BinaryOperator<UpperCardinality> upperOperator,
58 CardinalityInterval whenEmpty) { 68 CardinalityInterval whenEmpty) {
@@ -73,7 +83,10 @@ public record NonEmptyCardinalityInterval(int lowerBound, UpperCardinality upper
73 83
74 @Override 84 @Override
75 public String toString() { 85 public String toString() {
76 var closeBracket = upperBound instanceof UnboundedUpperCardinality ? ")" : "]"; 86 if (upperBound instanceof FiniteUpperCardinality finiteUpperCardinality &&
77 return "[%d..%s%s".formatted(lowerBound, upperBound, closeBracket); 87 finiteUpperCardinality.finiteUpperBound() == lowerBound) {
88 return "[%d]".formatted(lowerBound);
89 }
90 return "[%d..%s]".formatted(lowerBound, upperBound);
78 } 91 }
79} 92}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java
index a5634020..e3a334cd 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UnboundedUpperCardinality.java
@@ -7,6 +7,8 @@ package tools.refinery.store.representation.cardinality;
7 7
8import org.jetbrains.annotations.NotNull; 8import org.jetbrains.annotations.NotNull;
9 9
10// Singleton implementation, because there is only a single countable infinity.
11@SuppressWarnings("squid:S6548")
10public final class UnboundedUpperCardinality implements UpperCardinality { 12public final class UnboundedUpperCardinality implements UpperCardinality {
11 static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality(); 13 static final UnboundedUpperCardinality INSTANCE = new UnboundedUpperCardinality();
12 14
@@ -20,10 +22,17 @@ public final class UnboundedUpperCardinality implements UpperCardinality {
20 } 22 }
21 23
22 @Override 24 @Override
25 public UpperCardinality take(int count) {
26 return this;
27 }
28
29 @Override
23 public UpperCardinality multiply(UpperCardinality other) { 30 public UpperCardinality multiply(UpperCardinality other) {
24 return this; 31 return this;
25 } 32 }
26 33
34 // This should always be greater than any finite cardinality.
35 @SuppressWarnings("ComparatorMethodParameterNotUsed")
27 @Override 36 @Override
28 public int compareTo(@NotNull UpperCardinality upperCardinality) { 37 public int compareTo(@NotNull UpperCardinality upperCardinality) {
29 if (upperCardinality instanceof FiniteUpperCardinality) { 38 if (upperCardinality instanceof FiniteUpperCardinality) {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java
index 1e18dde0..17d1b292 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinalities.java
@@ -26,7 +26,7 @@ public final class UpperCardinalities {
26 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 26 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
27 } 27 }
28 28
29 public static UpperCardinality valueOf(int upperBound) { 29 public static UpperCardinality atMost(int upperBound) {
30 if (upperBound < 0) { 30 if (upperBound < 0) {
31 return UNBOUNDED; 31 return UNBOUNDED;
32 } 32 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java
index 5dbaa922..3f0db028 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/cardinality/UpperCardinality.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.representation.cardinality; 6package tools.refinery.store.representation.cardinality;
7 7
8import org.jetbrains.annotations.Nullable;
9
8public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality, 10public sealed interface UpperCardinality extends Comparable<UpperCardinality> permits FiniteUpperCardinality,
9 UnboundedUpperCardinality { 11 UnboundedUpperCardinality {
10 default UpperCardinality min(UpperCardinality other) { 12 default UpperCardinality min(UpperCardinality other) {
@@ -17,11 +19,14 @@ public sealed interface UpperCardinality extends Comparable<UpperCardinality> pe
17 19
18 UpperCardinality add(UpperCardinality other); 20 UpperCardinality add(UpperCardinality other);
19 21
22 @Nullable
23 UpperCardinality take(int count);
24
20 UpperCardinality multiply(UpperCardinality other); 25 UpperCardinality multiply(UpperCardinality other);
21 26
22 int compareToInt(int value); 27 int compareToInt(int value);
23 28
24 static UpperCardinality of(int upperBound) { 29 static UpperCardinality of(int upperBound) {
25 return UpperCardinalities.valueOf(upperBound); 30 return UpperCardinalities.atMost(upperBound);
26 } 31 }
27} 32}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
index 8cfd24d5..cb73d27e 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateCoderAdapter.java
@@ -17,7 +17,7 @@ public interface StateCoderAdapter extends ModelAdapter {
17 return calculateStateCode().objectCode(); 17 return calculateStateCode().objectCode();
18 } 18 }
19 19
20 static StateCoderBuilderImpl builder() { 20 static StateCoderBuilder builder() {
21 return new StateCoderBuilderImpl(); 21 return new StateCoderBuilderImpl();
22 } 22 }
23} 23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
index 3fd8c8d8..0f0023ed 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/StateEquivalenceChecker.java
@@ -6,7 +6,7 @@
6package tools.refinery.store.statecoding; 6package tools.refinery.store.statecoding;
7 7
8import org.eclipse.collections.api.set.primitive.IntSet; 8import org.eclipse.collections.api.set.primitive.IntSet;
9import tools.refinery.store.model.Interpretation; 9import tools.refinery.store.model.AnyInterpretation;
10 10
11import java.util.List; 11import java.util.List;
12 12
@@ -16,9 +16,6 @@ public interface StateEquivalenceChecker {
16 } 16 }
17 17
18 EquivalenceResult constructMorphism( 18 EquivalenceResult constructMorphism(
19 IntSet individuals, 19 IntSet individuals, List<? extends AnyInterpretation> interpretations1, ObjectCode code1,
20 List<? extends Interpretation<?>> interpretations1, 20 List<? extends AnyInterpretation> interpretations2, ObjectCode code2);
21 ObjectCode code1, List<?
22 extends Interpretation<?>> interpretations2,
23 ObjectCode code2);
24} 21}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
index 05b47c52..eed591e7 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/internal/StateCoderBuilderImpl.java
@@ -5,61 +5,61 @@
5 */ 5 */
6package tools.refinery.store.statecoding.internal; 6package tools.refinery.store.statecoding.internal;
7 7
8import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; 8import org.eclipse.collections.api.factory.primitive.IntSets;
9import org.eclipse.collections.api.set.primitive.MutableIntSet;
10import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.representation.AnySymbol; 12import tools.refinery.store.representation.AnySymbol;
12import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.statecoding.*; 14import tools.refinery.store.statecoding.StateCodeCalculatorFactory;
15import tools.refinery.store.statecoding.StateCoderBuilder;
16import tools.refinery.store.statecoding.StateCoderStoreAdapter;
17import tools.refinery.store.statecoding.StateEquivalenceChecker;
14import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator; 18import tools.refinery.store.statecoding.neighbourhood.NeighbourhoodCalculator;
15import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl; 19import tools.refinery.store.statecoding.stateequivalence.StateEquivalenceCheckerImpl;
16import tools.refinery.store.tuple.Tuple1; 20import tools.refinery.store.tuple.Tuple1;
17 21
18import java.util.*; 22import java.util.HashSet;
23import java.util.LinkedHashSet;
24import java.util.Set;
19 25
20public class StateCoderBuilderImpl implements StateCoderBuilder { 26public class StateCoderBuilderImpl extends AbstractModelAdapterBuilder<StateCoderStoreAdapter>
21 Set<AnySymbol> excluded = new HashSet<>(); 27 implements StateCoderBuilder {
22 IntHashSet individuals = new IntHashSet(); 28 private final Set<AnySymbol> excluded = new HashSet<>();
23 29 private final MutableIntSet individuals = IntSets.mutable.empty();
24 StateCodeCalculatorFactory calculator = NeighbourhoodCalculator::new; 30 private StateCodeCalculatorFactory calculator = NeighbourhoodCalculator::new;
25 StateEquivalenceChecker checker = new StateEquivalenceCheckerImpl(); 31 private StateEquivalenceChecker checker = new StateEquivalenceCheckerImpl();
26 32
27 @Override 33 @Override
28 public StateCoderBuilder exclude(AnySymbol symbol) { 34 public StateCoderBuilder exclude(AnySymbol symbol) {
35 checkNotConfigured();
29 excluded.add(symbol); 36 excluded.add(symbol);
30 return this; 37 return this;
31 } 38 }
32 39
33 @Override 40 @Override
34 public StateCoderBuilder individual(Tuple1 tuple) { 41 public StateCoderBuilder individual(Tuple1 tuple) {
42 checkNotConfigured();
35 individuals.add(tuple.get(0)); 43 individuals.add(tuple.get(0));
36 return this; 44 return this;
37 } 45 }
38 46
39 @Override 47 @Override
40 public StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker) { 48 public StateCoderBuilder stateEquivalenceChecker(StateEquivalenceChecker stateEquivalenceChecker) {
49 checkNotConfigured();
41 this.checker = stateEquivalenceChecker; 50 this.checker = stateEquivalenceChecker;
42 return this; 51 return this;
43 } 52 }
44 53
45 @Override 54 @Override
46 public StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory) { 55 public StateCoderBuilder stateCodeCalculatorFactory(StateCodeCalculatorFactory codeCalculatorFactory) {
56 checkNotConfigured();
47 this.calculator = codeCalculatorFactory; 57 this.calculator = codeCalculatorFactory;
48 return this; 58 return this;
49 } 59 }
50 60
51 @Override 61 @Override
52 public boolean isConfigured() { 62 protected StateCoderStoreAdapter doBuild(ModelStore store) {
53 return true;
54 }
55
56 @Override
57 public void configure(ModelStoreBuilder storeBuilder) {
58 // It does not modify the build process
59 }
60
61 @Override
62 public StateCoderStoreAdapter build(ModelStore store) {
63 Set<Symbol<?>> symbols = new LinkedHashSet<>(); 63 Set<Symbol<?>> symbols = new LinkedHashSet<>();
64 for (AnySymbol symbol : store.getSymbols()) { 64 for (AnySymbol symbol : store.getSymbols()) {
65 if (!excluded.contains(symbol) && (symbol instanceof Symbol<?> typed)) { 65 if (!excluded.contains(symbol) && (symbol instanceof Symbol<?> typed)) {
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
index 0a40a19d..5d390da2 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/AbstractNeighbourhoodCalculator.java
@@ -5,8 +5,10 @@
5 */ 5 */
6package tools.refinery.store.statecoding.neighbourhood; 6package tools.refinery.store.statecoding.neighbourhood;
7 7
8import org.eclipse.collections.api.factory.primitive.IntLongMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntLongMap;
8import org.eclipse.collections.api.set.primitive.IntSet; 10import org.eclipse.collections.api.set.primitive.IntSet;
9import org.eclipse.collections.impl.map.mutable.primitive.IntLongHashMap; 11import tools.refinery.store.model.AnyInterpretation;
10import tools.refinery.store.model.Interpretation; 12import tools.refinery.store.model.Interpretation;
11import tools.refinery.store.statecoding.ObjectCode; 13import tools.refinery.store.statecoding.ObjectCode;
12import tools.refinery.store.tuple.Tuple; 14import tools.refinery.store.tuple.Tuple;
@@ -15,25 +17,25 @@ import tools.refinery.store.tuple.Tuple0;
15import java.util.*; 17import java.util.*;
16 18
17public abstract class AbstractNeighbourhoodCalculator { 19public abstract class AbstractNeighbourhoodCalculator {
18 protected final List<Interpretation<?>> nullImpactValues; 20 protected final List<AnyInterpretation> nullImpactValues;
19 protected final LinkedHashMap<Interpretation<?>, long[]> impactValues; 21 protected final LinkedHashMap<AnyInterpretation, long[]> impactValues;
20 protected final IntLongHashMap individualHashValues; 22 protected final MutableIntLongMap individualHashValues = IntLongMaps.mutable.empty();
21 23
22 protected static final long PRIME = 31; 24 protected static final long PRIME = 31;
23 25
24 protected AbstractNeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations, IntSet individuals) { 26 protected AbstractNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
25 this.nullImpactValues = new ArrayList<>(); 27 this.nullImpactValues = new ArrayList<>();
26 this.impactValues = new LinkedHashMap<>(); 28 this.impactValues = new LinkedHashMap<>();
29 // Random isn't used for cryptographical purposes but just to assign distinguishable identifiers to symbols.
27 @SuppressWarnings("squid:S2245") 30 @SuppressWarnings("squid:S2245")
28 Random random = new Random(1); 31 Random random = new Random(1);
29 32
30 individualHashValues = new IntLongHashMap();
31 var individualsInOrder = individuals.toSortedList(Integer::compare); 33 var individualsInOrder = individuals.toSortedList(Integer::compare);
32 for(int i = 0; i<individualsInOrder.size(); i++) { 34 for(int i = 0; i<individualsInOrder.size(); i++) {
33 individualHashValues.put(individualsInOrder.get(i), random.nextLong()); 35 individualHashValues.put(individualsInOrder.get(i), random.nextLong());
34 } 36 }
35 37
36 for (Interpretation<?> interpretation : interpretations) { 38 for (AnyInterpretation interpretation : interpretations) {
37 int arity = interpretation.getSymbol().arity(); 39 int arity = interpretation.getSymbol().arity();
38 if (arity == 0) { 40 if (arity == 0) {
39 nullImpactValues.add(interpretation); 41 nullImpactValues.add(interpretation);
@@ -86,7 +88,7 @@ public abstract class AbstractNeighbourhoodCalculator {
86 protected long calculateModelCode(long lastSum) { 88 protected long calculateModelCode(long lastSum) {
87 long result = 0; 89 long result = 0;
88 for (var nullImpactValue : nullImpactValues) { 90 for (var nullImpactValue : nullImpactValues) {
89 result = result * PRIME + Objects.hashCode(nullImpactValue.get(Tuple0.INSTANCE)); 91 result = result * PRIME + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
90 } 92 }
91 result += lastSum; 93 result += lastSum;
92 return result; 94 return result;
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
index 2ffbef5e..c188a839 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/LazyNeighbourhoodCalculator.java
@@ -5,10 +5,12 @@
5 */ 5 */
6package tools.refinery.store.statecoding.neighbourhood; 6package tools.refinery.store.statecoding.neighbourhood;
7 7
8import org.eclipse.collections.api.factory.primitive.LongIntMaps;
8import org.eclipse.collections.api.map.primitive.LongIntMap; 9import org.eclipse.collections.api.map.primitive.LongIntMap;
10import org.eclipse.collections.api.map.primitive.MutableLongIntMap;
9import org.eclipse.collections.api.set.primitive.IntSet; 11import org.eclipse.collections.api.set.primitive.IntSet;
10import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap;
11import tools.refinery.store.map.Cursor; 12import tools.refinery.store.map.Cursor;
13import tools.refinery.store.model.AnyInterpretation;
12import tools.refinery.store.model.Interpretation; 14import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.statecoding.StateCodeCalculator; 15import tools.refinery.store.statecoding.StateCodeCalculator;
14import tools.refinery.store.statecoding.StateCoderResult; 16import tools.refinery.store.statecoding.StateCoderResult;
@@ -17,13 +19,13 @@ import tools.refinery.store.tuple.Tuple;
17import java.util.List; 19import java.util.List;
18 20
19public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator { 21public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator implements StateCodeCalculator {
20 public LazyNeighbourhoodCalculator(List<? extends Interpretation<?>> interpretations, IntSet individuals) { 22 public LazyNeighbourhoodCalculator(List<? extends AnyInterpretation> interpretations, IntSet individuals) {
21 super(interpretations, individuals); 23 super(interpretations, individuals);
22 } 24 }
23 25
24 public StateCoderResult calculateCodes() { 26 public StateCoderResult calculateCodes() {
25 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl(); 27 ObjectCodeImpl previousObjectCode = new ObjectCodeImpl();
26 LongIntHashMap prevHash2Amount = new LongIntHashMap(); 28 MutableLongIntMap prevHash2Amount = LongIntMaps.mutable.empty();
27 29
28 long lastSum; 30 long lastSum;
29 // All hash code is 0, except to the individuals. 31 // All hash code is 0, except to the individuals.
@@ -42,7 +44,7 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
42 } 44 }
43 constructNextObjectCodes(previousObjectCode, nextObjectCode, prevHash2Amount); 45 constructNextObjectCodes(previousObjectCode, nextObjectCode, prevHash2Amount);
44 46
45 LongIntHashMap nextHash2Amount = new LongIntHashMap(); 47 MutableLongIntMap nextHash2Amount = LongIntMaps.mutable.empty();
46 lastSum = calculateLastSum(previousObjectCode, nextObjectCode, prevHash2Amount, nextHash2Amount); 48 lastSum = calculateLastSum(previousObjectCode, nextObjectCode, prevHash2Amount, nextHash2Amount);
47 49
48 int nextSize = nextHash2Amount.size(); 50 int nextSize = nextHash2Amount.size();
@@ -60,7 +62,7 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
60 } 62 }
61 63
62 private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount, 64 private long calculateLastSum(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount,
63 LongIntHashMap nextHash2Amount) { 65 MutableLongIntMap nextHash2Amount) {
64 long lastSum = 0; 66 long lastSum = 0;
65 for (int i = 0; i < next.getSize(); i++) { 67 for (int i = 0; i < next.getSize(); i++) {
66 final long hash; 68 final long hash;
@@ -84,7 +86,7 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
84 86
85 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) { 87 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next, LongIntMap hash2Amount) {
86 for (var impactValueEntry : this.impactValues.entrySet()) { 88 for (var impactValueEntry : this.impactValues.entrySet()) {
87 Interpretation<?> interpretation = impactValueEntry.getKey(); 89 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
88 var cursor = interpretation.getAll(); 90 var cursor = interpretation.getAll();
89 int arity = interpretation.getSymbol().arity(); 91 int arity = interpretation.getSymbol().arity();
90 long[] impactValue = impactValueEntry.getValue(); 92 long[] impactValue = impactValueEntry.getValue();
@@ -114,7 +116,8 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
114 return amount == 1; 116 return amount == 1;
115 } 117 }
116 118
117 private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 119 private void lazyImpactCalculation1(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
120 long[] impactValues, Cursor<Tuple, ?> cursor) {
118 121
119 Tuple tuple = cursor.getKey(); 122 Tuple tuple = cursor.getKey();
120 int o = tuple.get(0); 123 int o = tuple.get(0);
@@ -129,7 +132,8 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
129 } 132 }
130 } 133 }
131 134
132 private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 135 private void lazyImpactCalculation2(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
136 long[] impactValues, Cursor<Tuple, ?> cursor) {
133 final Tuple tuple = cursor.getKey(); 137 final Tuple tuple = cursor.getKey();
134 final int o1 = tuple.get(0); 138 final int o1 = tuple.get(0);
135 final int o2 = tuple.get(1); 139 final int o2 = tuple.get(1);
@@ -155,7 +159,8 @@ public class LazyNeighbourhoodCalculator extends AbstractNeighbourhoodCalculator
155 } 159 }
156 } 160 }
157 161
158 private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 162 private void lazyImpactCalculationN(LongIntMap hash2Amount, ObjectCodeImpl previous, ObjectCodeImpl next,
163 long[] impactValues, Cursor<Tuple, ?> cursor) {
159 final Tuple tuple = cursor.getKey(); 164 final Tuple tuple = cursor.getKey();
160 165
161 final boolean[] uniques = new boolean[tuple.getSize()]; 166 final boolean[] uniques = new boolean[tuple.getSize()];
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
index 5b3e5ea3..785fda7a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/neighbourhood/NeighbourhoodCalculator.java
@@ -43,7 +43,7 @@ public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator imp
43 private long calculateLastSum(ObjectCode codes) { 43 private long calculateLastSum(ObjectCode codes) {
44 long result = 0; 44 long result = 0;
45 for (var nullImpactValue : nullImpactValues) { 45 for (var nullImpactValue : nullImpactValues) {
46 result = result * 31 + Objects.hashCode(nullImpactValue.get(Tuple0.INSTANCE)); 46 result = result * 31 + Objects.hashCode(((Interpretation<?>) nullImpactValue).get(Tuple0.INSTANCE));
47 } 47 }
48 48
49 for (int i = 0; i < codes.getSize(); i++) { 49 for (int i = 0; i < codes.getSize(); i++) {
@@ -56,7 +56,7 @@ public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator imp
56 56
57 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next) { 57 private void constructNextObjectCodes(ObjectCodeImpl previous, ObjectCodeImpl next) {
58 for (var impactValueEntry : this.impactValues.entrySet()) { 58 for (var impactValueEntry : this.impactValues.entrySet()) {
59 Interpretation<?> interpretation = impactValueEntry.getKey(); 59 Interpretation<?> interpretation = (Interpretation<?>) impactValueEntry.getKey();
60 var cursor = interpretation.getAll(); 60 var cursor = interpretation.getAll();
61 int arity = interpretation.getSymbol().arity(); 61 int arity = interpretation.getSymbol().arity();
62 long[] impactValue = impactValueEntry.getValue(); 62 long[] impactValue = impactValueEntry.getValue();
@@ -78,7 +78,8 @@ public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator imp
78 } 78 }
79 79
80 80
81 private void impactCalculation1(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 81 private void impactCalculation1(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
82 Cursor<Tuple, ?> cursor) {
82 83
83 Tuple tuple = cursor.getKey(); 84 Tuple tuple = cursor.getKey();
84 int o = tuple.get(0); 85 int o = tuple.get(0);
@@ -87,7 +88,8 @@ public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator imp
87 addHash(next, o, impactValues[0], tupleHash); 88 addHash(next, o, impactValues[0], tupleHash);
88 } 89 }
89 90
90 private void impactCalculation2(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 91 private void impactCalculation2(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
92 Cursor<Tuple, ?> cursor) {
91 final Tuple tuple = cursor.getKey(); 93 final Tuple tuple = cursor.getKey();
92 final int o1 = tuple.get(0); 94 final int o1 = tuple.get(0);
93 final int o2 = tuple.get(1); 95 final int o2 = tuple.get(1);
@@ -99,7 +101,8 @@ public class NeighbourhoodCalculator extends AbstractNeighbourhoodCalculator imp
99 addHash(next, o2, impactValues[1], tupleHash); 101 addHash(next, o2, impactValues[1], tupleHash);
100 } 102 }
101 103
102 private void impactCalculationN(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues, Cursor<Tuple, ?> cursor) { 104 private void impactCalculationN(ObjectCodeImpl previous, ObjectCodeImpl next, long[] impactValues,
105 Cursor<Tuple, ?> cursor) {
103 final Tuple tuple = cursor.getKey(); 106 final Tuple tuple = cursor.getKey();
104 107
105 Object value = cursor.getValue(); 108 Object value = cursor.getValue();
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
index decff1d5..0682e1a4 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairing.java
@@ -8,18 +8,14 @@ package tools.refinery.store.statecoding.stateequivalence;
8import org.eclipse.collections.api.factory.primitive.IntIntMaps; 8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap; 9import org.eclipse.collections.api.map.primitive.IntIntMap;
10import org.eclipse.collections.api.set.primitive.IntSet; 10import org.eclipse.collections.api.set.primitive.IntSet;
11import org.eclipse.collections.impl.list.Interval;
12import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet;
13 11
14import java.util.ArrayList; 12import java.util.*;
15import java.util.Arrays;
16import java.util.List;
17 13
18public class CombinationNodePairing implements NodePairing { 14public class CombinationNodePairing implements NodePairing {
19 private final int[] left; 15 private final int[] left;
20 private final int[] right; 16 private final int[] right;
21 17
22 CombinationNodePairing(IntSet left, IntHashSet right) { 18 CombinationNodePairing(IntSet left, IntSet right) {
23 this.left = left.toArray(); 19 this.left = left.toArray();
24 this.right = right.toArray(); 20 this.right = right.toArray();
25 21
@@ -32,59 +28,33 @@ public class CombinationNodePairing implements NodePairing {
32 return left.length; 28 return left.length;
33 } 29 }
34 30
35 static final int LIMIT = 5; 31 private static final int LIMIT = 5;
36 static final List<List<int[]>> permutations = new ArrayList<>(); 32 // Enum-based singleton used to delay generating all permutations until they are first needed.
37 33 @SuppressWarnings("squid:S6548")
38 /** 34 private enum PermutationsHolder {
39 * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a 35 INSTANCE;
40 * single permutation only. 36
41 * 37 final CombinationNodePairingPermutations permutations = new CombinationNodePairingPermutations(LIMIT);
42 * @param max The max number in the permutation
43 * @return A complete list of permutations of numbers 0...max, or a single permutation.
44 */
45 public static List<int[]> getPermutations(int max) {
46 if (max < permutations.size()) {
47 return permutations.get(max);
48 }
49 if (max == 0) {
50 List<int[]> result = new ArrayList<>();
51 result.add(new int[1]);
52 permutations.add(result);
53 return result;
54 }
55 List<int[]> result = new ArrayList<>();
56 List<int[]> previousPermutations = getPermutations(max - 1);
57 for (var permutation : previousPermutations) {
58 for (int pos = 0; pos <= max; pos++) {
59 int[] newPermutation = new int[max + 1];
60 System.arraycopy(permutation, 0, newPermutation, 0, pos);
61 newPermutation[pos] = max;
62 if (max - (pos + 1) >= 0)
63 System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1));
64 result.add(newPermutation);
65 }
66 }
67 permutations.add(result);
68 return result;
69 } 38 }
70 39
71 @Override 40 @Override
72 public List<IntIntMap> permutations() { 41 public List<IntIntMap> permutations() {
73 final var interval = Interval.zeroTo(this.size() - 1); 42 int limit = this.size();
43 Iterable<Integer> interval = () -> new IntervalIterator(limit);
74 44
75 if (isComplete()) { 45 if (isComplete()) {
76 final List<int[]> p = getPermutations(this.size() - 1); 46 final List<int[]> p = PermutationsHolder.INSTANCE.permutations.getPermutations(this.size() - 1);
77 return p.stream().map(x -> constructPermutationMap(interval, x)).toList(); 47 return p.stream().map(x -> constructPermutationMap(interval, x)).toList();
78 } else { 48 } else {
79 return List.of(constructTrivialMap(interval)); 49 return List.of(constructTrivialMap(interval));
80 } 50 }
81 } 51 }
82 52
83 private IntIntMap constructTrivialMap(Interval interval) { 53 private IntIntMap constructTrivialMap(Iterable<Integer> interval) {
84 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]); 54 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[r]);
85 } 55 }
86 56
87 private IntIntMap constructPermutationMap(Interval interval, int[] permutation) { 57 private IntIntMap constructPermutationMap(Iterable<Integer> interval, int[] permutation) {
88 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]); 58 return IntIntMaps.immutable.from(interval, l -> left[l], r -> right[permutation[r]]);
89 } 59 }
90 60
@@ -92,4 +62,28 @@ public class CombinationNodePairing implements NodePairing {
92 public boolean isComplete() { 62 public boolean isComplete() {
93 return this.size() <= LIMIT; 63 return this.size() <= LIMIT;
94 } 64 }
65
66 private static class IntervalIterator implements Iterator<Integer> {
67 private final int limit;
68 private int value = 0;
69
70 private IntervalIterator(int max) {
71 this.limit = max;
72 }
73
74 @Override
75 public boolean hasNext() {
76 return value < limit;
77 }
78
79 @Override
80 public Integer next() {
81 if (value >= limit) {
82 throw new NoSuchElementException("End of interval");
83 }
84 int next = value;
85 value++;
86 return next;
87 }
88 }
95} 89}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
new file mode 100644
index 00000000..eacd3a2a
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/CombinationNodePairingPermutations.java
@@ -0,0 +1,57 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.store.statecoding.stateequivalence;
7
8import java.util.ArrayList;
9import java.util.List;
10
11class CombinationNodePairingPermutations {
12 private final List<List<int[]>> permutations = new ArrayList<>();
13
14 public CombinationNodePairingPermutations(int max) {
15 initializePermutations(max);
16 }
17
18 public List<int[]> getPermutations(int max) {
19 if (max >= permutations.size()) {
20 throw new IllegalArgumentException("Only permutations up to %d elements are supported".formatted(max));
21 }
22 return permutations.get(max);
23 }
24
25 /**
26 * Generates and stores permutations up to a given size. If the number would be more than a limit, it provides a
27 * single permutation only.
28 *
29 * @param max The max number in the permutation
30 * @return A complete list of permutations of numbers 0...max, or a single permutation.
31 */
32 private List<int[]> initializePermutations(int max) {
33 if (max < permutations.size()) {
34 return permutations.get(max);
35 }
36 if (max == 0) {
37 List<int[]> result = new ArrayList<>();
38 result.add(new int[1]);
39 permutations.add(result);
40 return result;
41 }
42 List<int[]> result = new ArrayList<>();
43 List<int[]> previousPermutations = initializePermutations(max - 1);
44 for (var permutation : previousPermutations) {
45 for (int pos = 0; pos <= max; pos++) {
46 int[] newPermutation = new int[max + 1];
47 System.arraycopy(permutation, 0, newPermutation, 0, pos);
48 newPermutation[pos] = max;
49 if (max - (pos + 1) >= 0)
50 System.arraycopy(permutation, pos + 1, newPermutation, pos + 1 + 1, max - (pos + 1));
51 result.add(newPermutation);
52 }
53 }
54 permutations.add(result);
55 return result;
56 }
57}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
index 7e5db7a3..f45f0d2e 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/NodePairing.java
@@ -6,18 +6,18 @@
6package tools.refinery.store.statecoding.stateequivalence; 6package tools.refinery.store.statecoding.stateequivalence;
7 7
8import org.eclipse.collections.api.map.primitive.IntIntMap; 8import org.eclipse.collections.api.map.primitive.IntIntMap;
9import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet; 9import org.eclipse.collections.api.set.primitive.IntSet;
10 10
11import java.util.List; 11import java.util.List;
12 12
13public interface NodePairing { 13public interface NodePairing {
14
15 int size(); 14 int size();
15
16 List<IntIntMap> permutations(); 16 List<IntIntMap> permutations();
17 17
18 boolean isComplete(); 18 boolean isComplete();
19 19
20 static NodePairing constructNodePairing(IntHashSet left, IntHashSet right){ 20 static NodePairing constructNodePairing(IntSet left, IntSet right){
21 if(left.size() != right.size()) { 21 if(left.size() != right.size()) {
22 return null; 22 return null;
23 } 23 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
index 6be0f3e7..bc4d723a 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/PermutationMorphism.java
@@ -12,12 +12,12 @@ import java.util.List;
12 12
13public class PermutationMorphism implements Morphism { 13public class PermutationMorphism implements Morphism {
14 private final IntIntMap object2PermutationGroup; 14 private final IntIntMap object2PermutationGroup;
15 private final List<List<IntIntMap>> permutationsGroups; 15 private final List<? extends List<? extends IntIntMap>> permutationsGroups;
16 private final int[] selection; 16 private final int[] selection;
17 private boolean hasNext; 17 private boolean hasNext;
18 18
19 PermutationMorphism(IntIntMap object2PermutationGroup, 19 PermutationMorphism(IntIntMap object2PermutationGroup,
20 List<List<IntIntMap>> permutationsGroups) { 20 List<? extends List<? extends IntIntMap>> permutationsGroups) {
21 this.object2PermutationGroup = object2PermutationGroup; 21 this.object2PermutationGroup = object2PermutationGroup;
22 this.permutationsGroups = permutationsGroups; 22 this.permutationsGroups = permutationsGroups;
23 23
diff --git a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
index ef0d76a7..5a62d8a0 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/statecoding/stateequivalence/StateEquivalenceCheckerImpl.java
@@ -6,11 +6,14 @@
6package tools.refinery.store.statecoding.stateequivalence; 6package tools.refinery.store.statecoding.stateequivalence;
7 7
8import org.eclipse.collections.api.factory.primitive.IntIntMaps; 8import org.eclipse.collections.api.factory.primitive.IntIntMaps;
9import org.eclipse.collections.api.factory.primitive.IntSets;
10import org.eclipse.collections.api.factory.primitive.LongObjectMaps;
9import org.eclipse.collections.api.map.primitive.IntIntMap; 11import org.eclipse.collections.api.map.primitive.IntIntMap;
12import org.eclipse.collections.api.map.primitive.MutableIntIntMap;
13import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
10import org.eclipse.collections.api.set.primitive.IntSet; 14import org.eclipse.collections.api.set.primitive.IntSet;
11import org.eclipse.collections.impl.map.mutable.primitive.IntIntHashMap; 15import org.eclipse.collections.api.set.primitive.MutableIntSet;
12import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap; 16import tools.refinery.store.model.AnyInterpretation;
13import org.eclipse.collections.impl.set.mutable.primitive.IntHashSet;
14import tools.refinery.store.model.Interpretation; 17import tools.refinery.store.model.Interpretation;
15import tools.refinery.store.statecoding.Morphism; 18import tools.refinery.store.statecoding.Morphism;
16import tools.refinery.store.statecoding.ObjectCode; 19import tools.refinery.store.statecoding.ObjectCode;
@@ -26,12 +29,11 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
26 29
27 @Override 30 @Override
28 public EquivalenceResult constructMorphism(IntSet individuals, 31 public EquivalenceResult constructMorphism(IntSet individuals,
29 List<? extends Interpretation<?>> interpretations1, 32 List<? extends AnyInterpretation> interpretations1,
30 ObjectCode code1, 33 ObjectCode code1,
31 List<? extends Interpretation<?>> interpretations2, 34 List<? extends AnyInterpretation> interpretations2,
32 ObjectCode code2) 35 ObjectCode code2) {
33 { 36 MutableIntIntMap object2PermutationGroup = IntIntMaps.mutable.empty();
34 IntIntHashMap object2PermutationGroup = new IntIntHashMap();
35 List<List<IntIntMap>> permutationsGroups = new ArrayList<>(); 37 List<List<IntIntMap>> permutationsGroups = new ArrayList<>();
36 38
37 final EquivalenceResult permutations = constructPermutationNavigation(individuals, 39 final EquivalenceResult permutations = constructPermutationNavigation(individuals,
@@ -50,7 +52,7 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
50 return permutations; 52 return permutations;
51 } 53 }
52 54
53 if(tried >= LIMIT) { 55 if (tried >= LIMIT) {
54 return EquivalenceResult.UNKNOWN; 56 return EquivalenceResult.UNKNOWN;
55 } 57 }
56 58
@@ -58,22 +60,22 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
58 tried++; 60 tried++;
59 } while (hasNext); 61 } while (hasNext);
60 62
61 if(permutations == EquivalenceResult.UNKNOWN) { 63 if (permutations == EquivalenceResult.UNKNOWN) {
62 return EquivalenceResult.UNKNOWN; 64 return EquivalenceResult.UNKNOWN;
63 } else { 65 } else {
64 return EquivalenceResult.DIFFERENT; 66 return EquivalenceResult.DIFFERENT;
65 } 67 }
66 } 68 }
67 69
68 private LongObjectHashMap<IntHashSet> indexByHash(ObjectCode code, IntSet individuals) { 70 private MutableLongObjectMap<MutableIntSet> indexByHash(ObjectCode code, IntSet individuals) {
69 LongObjectHashMap<IntHashSet> result = new LongObjectHashMap<>(); 71 MutableLongObjectMap<MutableIntSet> result = LongObjectMaps.mutable.empty();
70 for (int o = 0; o < code.getSize(); o++) { 72 for (int o = 0; o < code.getSize(); o++) {
71 if(! individuals.contains(o)){ 73 if (!individuals.contains(o)) {
72 long hash = code.get(o); 74 long hash = code.get(o);
73 if(hash != 0) { 75 if (hash != 0) {
74 var equivalenceClass = result.get(hash); 76 var equivalenceClass = result.get(hash);
75 if (equivalenceClass == null) { 77 if (equivalenceClass == null) {
76 equivalenceClass = new IntHashSet(); 78 equivalenceClass = IntSets.mutable.empty();
77 result.put(hash, equivalenceClass); 79 result.put(hash, equivalenceClass);
78 } 80 }
79 equivalenceClass.add(o); 81 equivalenceClass.add(o);
@@ -83,11 +85,9 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
83 return result; 85 return result;
84 } 86 }
85 87
86 private EquivalenceResult constructPermutationNavigation(IntSet individuals, 88 private EquivalenceResult constructPermutationNavigation(
87 LongObjectHashMap<IntHashSet> map1, 89 IntSet individuals, MutableLongObjectMap<MutableIntSet> map1, MutableLongObjectMap<MutableIntSet> map2,
88 LongObjectHashMap<IntHashSet> map2, 90 MutableIntIntMap object2OptionIndex, List<List<IntIntMap>> listOfOptions) {
89 IntIntHashMap object2OptionIndex,
90 List<List<IntIntMap>> listOfOptions) {
91 if (map1.size() != map2.size()) { 91 if (map1.size() != map2.size()) {
92 return EquivalenceResult.DIFFERENT; 92 return EquivalenceResult.DIFFERENT;
93 } 93 }
@@ -116,27 +116,28 @@ public class StateEquivalenceCheckerImpl implements StateEquivalenceChecker {
116 listOfOptions.add(pairing.permutations()); 116 listOfOptions.add(pairing.permutations());
117 } 117 }
118 118
119 individuals.forEach(o -> listOfOptions.add(o,List.of(IntIntMaps.immutable.of(o,o)))); 119 individuals.forEach(o -> listOfOptions.add(o, List.of(IntIntMaps.immutable.of(o, o))));
120 120
121 if(allComplete) { 121 if (allComplete) {
122 return EquivalenceResult.ISOMORPHIC; 122 return EquivalenceResult.ISOMORPHIC;
123 } else { 123 } else {
124 return EquivalenceResult.UNKNOWN; 124 return EquivalenceResult.UNKNOWN;
125 } 125 }
126 } 126 }
127 127
128 private boolean testMorphism(List<? extends Interpretation<?>> s, List<? extends Interpretation<?>> t, Morphism m) { 128 private boolean testMorphism(List<? extends AnyInterpretation> s, List<? extends AnyInterpretation> t,
129 Morphism m) {
129 for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) { 130 for (int interpretationIndex = 0; interpretationIndex < s.size(); interpretationIndex++) {
130 var sI = s.get(interpretationIndex); 131 var sI = s.get(interpretationIndex);
131 var tI = t.get(interpretationIndex); 132 var tI = t.get(interpretationIndex);
132 133
133 var cursor = sI.getAll(); 134 var cursor = ((Interpretation<?>) sI).getAll();
134 while (cursor.move()) { 135 while (cursor.move()) {
135 final Tuple sTuple = cursor.getKey(); 136 final Tuple sTuple = cursor.getKey();
136 final Object sValue = cursor.getValue(); 137 final Object sValue = cursor.getValue();
137 138
138 final Tuple tTuple = apply(sTuple, m); 139 final Tuple tTuple = apply(sTuple, m);
139 final Object tValue = tI.get(tTuple); 140 final Object tValue = ((Interpretation<?>) tI).get(tTuple);
140 141
141 if (!Objects.equals(sValue, tValue)) { 142 if (!Objects.equals(sValue, tValue)) {
142 return false; 143 return false;
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java
index aae7b344..e9761763 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple.java
@@ -12,6 +12,8 @@ public sealed interface Tuple extends Comparable<Tuple> permits Tuple0, Tuple1,
12 12
13 int get(int element); 13 int get(int element);
14 14
15 Tuple set(int element, int value);
16
15 @Override 17 @Override
16 default int compareTo(@NotNull Tuple other) { 18 default int compareTo(@NotNull Tuple other) {
17 int size = getSize(); 19 int size = getSize();
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java
index a9aa9bf2..5f525798 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple0.java
@@ -29,6 +29,11 @@ public final class Tuple0 implements Tuple {
29 } 29 }
30 30
31 @Override 31 @Override
32 public Tuple set(int element, int value) {
33 throw new IndexOutOfBoundsException(element);
34 }
35
36 @Override
32 public String toString() { 37 public String toString() {
33 return TUPLE_BEGIN + TUPLE_END; 38 return TUPLE_BEGIN + TUPLE_END;
34 } 39 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java
index 388ee3a9..fb9497d2 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java
@@ -38,6 +38,14 @@ public final class Tuple1 implements Tuple {
38 } 38 }
39 39
40 @Override 40 @Override
41 public Tuple set(int element, int value) {
42 if (element == 0) {
43 return Tuple.of(value);
44 }
45 throw new IndexOutOfBoundsException(element);
46 }
47
48 @Override
41 public String toString() { 49 public String toString() {
42 return TUPLE_BEGIN + value0 + TUPLE_END; 50 return TUPLE_BEGIN + value0 + TUPLE_END;
43 } 51 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java
index 6d886fd3..2213df97 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple2.java
@@ -25,6 +25,15 @@ public record Tuple2(int value0, int value1) implements Tuple {
25 } 25 }
26 26
27 @Override 27 @Override
28 public Tuple set(int element, int value) {
29 return switch (element) {
30 case 0 -> Tuple.of(value, value1);
31 case 1 -> Tuple.of(value0, value);
32 default -> throw new ArrayIndexOutOfBoundsException(element);
33 };
34 }
35
36 @Override
28 public String toString() { 37 public String toString() {
29 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_END; 38 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_END;
30 } 39 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java
index 734e45c2..417770e8 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple3.java
@@ -26,6 +26,16 @@ public record Tuple3(int value0, int value1, int value2) implements Tuple {
26 } 26 }
27 27
28 @Override 28 @Override
29 public Tuple set(int element, int value) {
30 return switch (element) {
31 case 0 -> Tuple.of(value, value1, value2);
32 case 1 -> Tuple.of(value0, value, value2);
33 case 2 -> Tuple.of(value0, value1, value);
34 default -> throw new ArrayIndexOutOfBoundsException(element);
35 };
36 }
37
38 @Override
29 public String toString() { 39 public String toString() {
30 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_END; 40 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_END;
31 } 41 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java
index e1b93e7b..c4915198 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple4.java
@@ -27,6 +27,17 @@ public record Tuple4(int value0, int value1, int value2, int value3) implements
27 } 27 }
28 28
29 @Override 29 @Override
30 public Tuple set(int element, int value) {
31 return switch (element) {
32 case 0 -> Tuple.of(value, value1, value2, value3);
33 case 1 -> Tuple.of(value0, value, value2, value3);
34 case 2 -> Tuple.of(value0, value1, value, value3);
35 case 3 -> Tuple.of(value0, value1, value2, value);
36 default -> throw new ArrayIndexOutOfBoundsException(element);
37 };
38 }
39
40 @Override
30 public String toString() { 41 public String toString() {
31 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_SEPARATOR + value3 + 42 return TUPLE_BEGIN + value0 + TUPLE_SEPARATOR + value1 + TUPLE_SEPARATOR + value2 + TUPLE_SEPARATOR + value3 +
32 TUPLE_END; 43 TUPLE_END;
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 b66af491..b42b4b6a 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
@@ -32,6 +32,16 @@ public final class TupleN implements Tuple {
32 } 32 }
33 33
34 @Override 34 @Override
35 public Tuple set(int element, int value) {
36 int size = getSize();
37 var newValues = new int[size];
38 for (int i = 0; i < size; i++) {
39 newValues[i] = element == i ? value : values[i];
40 }
41 return Tuple.of(newValues);
42 }
43
44 @Override
35 public String toString() { 45 public String toString() {
36 var valuesString = Arrays.stream(values) 46 var valuesString = Arrays.stream(values)
37 .mapToObj(Integer::toString) 47 .mapToObj(Integer::toString)
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
index 78ad2ad7..2e302663 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java
@@ -20,6 +20,10 @@ public class CycleDetectingMapper<T, R> {
20 20
21 private final Map<T, R> results = new HashMap<>(); 21 private final Map<T, R> results = new HashMap<>();
22 22
23 public CycleDetectingMapper(Function<T, R> doMap) {
24 this(Objects::toString, doMap);
25 }
26
23 public CycleDetectingMapper(Function<T, String> getName, Function<T, R> doMap) { 27 public CycleDetectingMapper(Function<T, String> getName, Function<T, R> doMap) {
24 this.getName = getName; 28 this.getName = getName;
25 this.doMap = doMap; 29 this.doMap = doMap;
diff --git a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java
index e61f7b36..e403eec2 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/representation/cardinality/UpperCardinalitiesTest.java
@@ -8,9 +8,6 @@ package tools.refinery.store.representation.cardinality;
8import org.junit.jupiter.api.Test; 8import org.junit.jupiter.api.Test;
9import org.junit.jupiter.params.ParameterizedTest; 9import org.junit.jupiter.params.ParameterizedTest;
10import org.junit.jupiter.params.provider.ValueSource; 10import org.junit.jupiter.params.provider.ValueSource;
11import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
12import tools.refinery.store.representation.cardinality.UnboundedUpperCardinality;
13import tools.refinery.store.representation.cardinality.UpperCardinalities;
14 11
15import static org.hamcrest.MatcherAssert.assertThat; 12import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.equalTo; 13import static org.hamcrest.Matchers.equalTo;
@@ -20,14 +17,14 @@ class UpperCardinalitiesTest {
20 @ParameterizedTest 17 @ParameterizedTest
21 @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE}) 18 @ValueSource(ints = {0, 1, 255, 256, 1000, Integer.MAX_VALUE})
22 void valueOfBoundedTest(int value) { 19 void valueOfBoundedTest(int value) {
23 var upperCardinality = UpperCardinalities.valueOf(value); 20 var upperCardinality = UpperCardinalities.atMost(value);
24 assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class)); 21 assertThat(upperCardinality, instanceOf(FiniteUpperCardinality.class));
25 assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value)); 22 assertThat(((FiniteUpperCardinality) upperCardinality).finiteUpperBound(), equalTo(value));
26 } 23 }
27 24
28 @Test 25 @Test
29 void valueOfUnboundedTest() { 26 void valueOfUnboundedTest() {
30 var upperCardinality = UpperCardinalities.valueOf(-1); 27 var upperCardinality = UpperCardinalities.atMost(-1);
31 assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class)); 28 assertThat(upperCardinality, instanceOf(UnboundedUpperCardinality.class));
32 } 29 }
33} 30}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
index 25b5dca1..f5ffc18d 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/statecoding/ExperimentalSetupTest.java
@@ -5,7 +5,8 @@
5 */ 5 */
6package tools.refinery.store.statecoding; 6package tools.refinery.store.statecoding;
7 7
8import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap; 8import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
9import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
9import org.junit.jupiter.api.Tag; 10import org.junit.jupiter.api.Tag;
10import org.junit.jupiter.api.Test; 11import org.junit.jupiter.api.Test;
11import org.junit.jupiter.params.ParameterizedTest; 12import org.junit.jupiter.params.ParameterizedTest;
@@ -24,7 +25,6 @@ import java.util.Set;
24import static org.junit.jupiter.api.Assertions.assertEquals; 25import static org.junit.jupiter.api.Assertions.assertEquals;
25import static org.junit.jupiter.api.Assertions.assertTrue; 26import static org.junit.jupiter.api.Assertions.assertTrue;
26 27
27
28class ExperimentalSetupTest { 28class ExperimentalSetupTest {
29 static class ExperimentalSetupResult { 29 static class ExperimentalSetupResult {
30 int versions = 0; 30 int versions = 0;
@@ -61,7 +61,7 @@ class ExperimentalSetupTest {
61 .build(); 61 .build();
62 62
63 Set<Version> versions = new HashSet<>(); 63 Set<Version> versions = new HashSet<>();
64 IntObjectHashMap<List<Version>> codes = new IntObjectHashMap<>(); 64 MutableIntObjectMap<List<Version>> codes = IntObjectMaps.mutable.empty();
65 65
66 var empty = store.createEmptyModel(); 66 var empty = store.createEmptyModel();
67 if (!permuteTypes) { 67 if (!permuteTypes) {
@@ -123,8 +123,9 @@ class ExperimentalSetupTest {
123 return result; 123 return result;
124 } 124 }
125 125
126 private static void saveAsNewVersion(Set<Version> versions, IntObjectHashMap<List<Version>> codes, 126 private static void saveAsNewVersion(Set<Version> versions, MutableIntObjectMap<List<Version>> codes,
127 StateCoderStoreAdapter storeAdapter, ExperimentalSetupResult result, Model model) { 127 StateCoderStoreAdapter storeAdapter, ExperimentalSetupResult result,
128 Model model) {
128 Version version1 = model.commit(); 129 Version version1 = model.commit();
129 130
130 var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode(); 131 var stateCode = model.getAdapter(StateCoderAdapter.class).calculateStateCode();
diff --git a/subprojects/viatra-runtime-localsearch/about.html b/subprojects/viatra-runtime-localsearch/about.html
new file mode 100644
index 00000000..d1d5593a
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/about.html
@@ -0,0 +1,26 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
2<html>
3<!--
4 Copyright (c) 2017, Eclipse.org Foundation, Inc.
5
6 SPDX-License-Identifier: LicenseRef-EPL-Steward
7-->
8<head>
9<title>About</title>
10<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
11</head>
12<body lang="EN-US">
13<h2>About This Content</h2>
14
15<p>March 18, 2019</p>
16<h3>License</h3>
17
18<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
19Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available at <a href="http://www.eclipse.org/org/documents/epl-v20.php">http://www.eclipse.org/legal/epl-v20.html</a>.
20For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
21
22<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
23apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
24indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
25</body>
26</html>
diff --git a/subprojects/viatra-runtime-localsearch/build.gradle.kts b/subprojects/viatra-runtime-localsearch/build.gradle.kts
new file mode 100644
index 00000000..31c0c634
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/build.gradle.kts
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 implementation(project(":refinery-viatra-runtime"))
13 implementation(libs.slf4j.log4j)
14}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java
new file mode 100644
index 00000000..bfb76d9a
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch;
10
11import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter;
12import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher;
13import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
14import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
15import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
16
17import java.util.Optional;
18import java.util.function.Consumer;
19
20/**
21 * @since 2.0
22 */
23public final class ExecutionLoggerAdapter implements ILocalSearchAdapter {
24
25 volatile String indentation = "";
26 private final Consumer<String> outputConsumer;
27
28 public ExecutionLoggerAdapter(Consumer<String> outputConsumer) {
29 this.outputConsumer = outputConsumer;
30 }
31
32 private void logMessage(String message) {
33 outputConsumer.accept(message);
34 }
35
36 private void logMessage(String message, Object...args) {
37 outputConsumer.accept(String.format(message, args));
38 }
39
40 @Override
41 public void patternMatchingStarted(LocalSearchMatcher lsMatcher) {
42 logMessage(indentation + "[ START] " + lsMatcher.getQuerySpecification().getFullyQualifiedName());
43 }
44
45 @Override
46 public void noMoreMatchesAvailable(LocalSearchMatcher lsMatcher) {
47 logMessage(indentation + "[FINISH] " + lsMatcher.getQuerySpecification().getFullyQualifiedName());
48 }
49
50 @Override
51 public void planChanged(Optional<SearchPlan> oldPlan, Optional<SearchPlan> newPlan) {
52 logMessage(indentation + "[ PLAN] " + newPlan.map(p -> p.getSourceBody().getPattern().getFullyQualifiedName()).orElse(""));
53 logMessage(indentation + newPlan.map(SearchPlan::toString).map(s -> s.replace("\n", "\n" + indentation)).orElse(""));
54 }
55
56 @Override
57 public void operationSelected(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isBacktrack) {
58 String category = isBacktrack ? "[ BACK] " : "[SELECT] ";
59 logMessage(indentation + category + operation.toString());
60 if (operation instanceof IPatternMatcherOperation) {
61 indentation = indentation + "\t";
62 }
63 }
64
65 @Override
66 public void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame,
67 boolean isSuccessful) {
68 if (operation instanceof IPatternMatcherOperation && indentation.length() > 0) {
69 indentation = indentation.substring(1);
70 }
71 logMessage(indentation + "[ %s] %s %s", isSuccessful ? "OK" : "NO", operation.toString(), frame.toString());
72 }
73
74 @Override
75 public void matchFound(SearchPlan plan, MatchingFrame frame) {
76 logMessage(indentation + "[ MATCH] " + plan.getSourceBody().getPattern().getFullyQualifiedName() + " " + frame.toString());
77 }
78
79 @Override
80 public void duplicateMatchFound(MatchingFrame frame) {
81 logMessage(indentation + "[ DUPL.] " + frame.toString());
82 }
83}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java
new file mode 100644
index 00000000..bdbc663c
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Akos Horvath, Gergely Varro Zoltan Ujhelyi 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.localsearch;
11
12import tools.refinery.viatra.runtime.matchers.tuple.IModifiableTuple;
13import tools.refinery.viatra.runtime.matchers.tuple.VolatileTuple;
14import tools.refinery.viatra.runtime.matchers.util.Preconditions;
15
16import java.util.Arrays;
17import java.util.stream.Collectors;
18
19/**
20 * A MatchingFrame is a Volatile Tuple implementation used by the local search engine internally.
21 */
22public class MatchingFrame extends VolatileTuple implements IModifiableTuple {
23
24 /**
25 * The array that physically holds the values.
26 */
27 private Object[] frame;
28
29 /**
30 * @since 1.7
31 */
32 public MatchingFrame(int frameSize) {
33 this.frame = new Object[frameSize];
34 }
35
36 /**
37 * Creates a copy of another matching frame; the two frames can be updated separately
38 * @param other
39 * @since 1.7
40 */
41 public MatchingFrame(MatchingFrame other) {
42 this.frame = Arrays.copyOf(other.frame, other.frame.length);
43 }
44
45
46
47 /**
48 * Returns the value stored inside the matching frame.
49 *
50 * @param position
51 * @return the element stored in the selected position in the frame, or null if it is not yet set
52 * @throws IndexOutOfBoundsException
53 * if position is negative
54 * @throws IllegalArgumentException
55 * if the position is larger then the length of the frame
56 */
57 public Object getValue(int position) {
58 Preconditions.checkElementIndex(position, frame.length);
59 return frame[position];
60 }
61
62 /**
63 * Sets the value of the variable at the given position. For internal use in LS matching only.
64 *
65 * @param position the position of the variable within the frame
66 * @param value the value to be set for the variable
67 */
68 public void setValue(int position, Object value) {
69 Preconditions.checkElementIndex(position, frame.length);
70 frame[position] = value;
71 }
72
73 public boolean testAndSetValue(Integer position, Object value) {
74 Preconditions.checkElementIndex(position, frame.length);
75 if (frame[position] == null) {
76 frame[position] = value;
77 return true;
78 } else {
79 return frame[position].equals(value);
80 }
81 }
82
83 @Override
84 public String toString() {
85 return Arrays.stream(frame).map(this::stringRepresentation).collect(Collectors.joining(", ", "[", "]"));
86 }
87
88 private String stringRepresentation(Object obj) {
89 if (obj == null) {
90 return "_";
91 }
92 return obj.toString();
93 }
94
95 @Override
96 public int getSize() {
97 return frame.length;
98 }
99
100 @Override
101 public Object get(int index) {
102 return getValue(index);
103 }
104
105 @Override
106 public Object[] getElements() {
107 return Arrays.copyOf(frame, frame.length);
108 }
109
110 @Override
111 public void set(int index, Object value) {
112 frame[index] = value;
113 }
114}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java
new file mode 100644
index 00000000..c239e766
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java
@@ -0,0 +1,33 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.exceptions;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12
13/**
14 * @author Zoltan Ujhelyi, Akos Horvath
15 *
16 */
17public class LocalSearchException extends ViatraQueryRuntimeException {
18
19 private static final long serialVersionUID = -2585896573351435974L;
20
21 public static final String PLAN_EXECUTION_ERROR = "Error while executing search plan";
22 public static final String TYPE_ERROR = "Invalid type of variable";
23
24 public LocalSearchException(String description, Throwable rootException) {
25 super(description, rootException);
26 }
27
28 public LocalSearchException(String description) {
29 super(description);
30 }
31
32
33}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java
new file mode 100644
index 00000000..0cabeb97
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import java.util.HashSet;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18
19/**
20 * Immutable data that represents the role of a pattern call within an LS query plan.
21 *
22 * <p> The call is expressed as the {@link PConstraint} {@link #call} (implementing {@link IQueryReference}),
23 * while the stored {@link #adornment} records the way it will be used within a search plan (specifically,
24 * pattern parameters within the adornment will have their values known at the point of evaluating the constraint).
25 *
26 *
27 * @author Gabor Bergmann
28 * @since 2.1
29 */
30public class CallWithAdornment {
31 private final IQueryReference call;
32 private final Set<PParameter> adornment;
33
34 public CallWithAdornment(IQueryReference call, Set<PParameter> adornment) {
35 this.call = call;
36 this.adornment = new HashSet<>(adornment);
37 }
38
39 public IQueryReference getCall() {
40 return call;
41 }
42
43 public Set<PParameter> getAdornment() {
44 return adornment;
45 }
46
47
48 public PQuery getReferredQuery() {
49 return call.getReferredQuery();
50 }
51
52 public MatcherReference getMatcherReference() {
53 return new MatcherReference(getReferredQuery(), adornment);
54 }
55}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java
new file mode 100644
index 00000000..f4b28ed0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Peter Lunk, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import java.util.List;
12
13/**
14 * @author Zoltan Ujhelyi
15 *
16 */
17public interface ILocalSearchAdaptable {
18
19 List<ILocalSearchAdapter> getAdapters();
20
21 void addAdapter(ILocalSearchAdapter adapter);
22
23 void removeAdapter(ILocalSearchAdapter adapter);
24
25 void removeAdapters(List<ILocalSearchAdapter> adapter);
26
27 void addAdapters(List<ILocalSearchAdapter> adapter);
28
29} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java
new file mode 100644
index 00000000..df64b5f1
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java
@@ -0,0 +1,120 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import java.util.Optional;
12
13import tools.refinery.viatra.runtime.localsearch.ExecutionLoggerAdapter;
14import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
15import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
16import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
17import tools.refinery.viatra.runtime.localsearch.profiler.LocalSearchProfilerAdapter;
18
19
20/**
21 * A local search adapter allows external code to follow the internal executions of the local search matcher. Possible
22 * implementations of the interface include profilers and debuggers.
23 * <p>
24 * <strong>EXPERIMENTAL</strong>. A few shortcomings have been found for this interface late during the development
25 * lifecycle of version 2.0 whose solution might need breaking possible future implementors. Because of this, right now
26 * it is not recommended to provide implementations outside of VIATRA. If necessary, have a look at the built-in
27 * adapters that should fulfill most cases in the meantime. See bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=535101
28 * and https://bugs.eclipse.org/bugs/show_bug.cgi?id=535102 for details.
29 *
30 * @author Marton Bur
31 * @see ExecutionLoggerAdapter
32 * @see LocalSearchProfilerAdapter
33 *
34 */
35public interface ILocalSearchAdapter {
36
37 /**
38 *
39 * @since 1.2
40 */
41 default void adapterRegistered(ILocalSearchAdaptable adaptable) {};
42 /**
43 *
44 * @since 1.2
45 */
46 default void adapterUnregistered(ILocalSearchAdaptable adaptable) {};
47
48 /**
49 * Callback method to indicate the start of a matching process
50 *
51 * @param lsMatcher the local search matcher that starts the matching
52 */
53 default void patternMatchingStarted(LocalSearchMatcher lsMatcher) {};
54
55 /**
56 * Callback method to indicate the end of a matching process
57 * </p>
58 * <strong>WARNING</strong>: It is not guaranteed that this method will be called;
59 * it is possible that a match process will end after a match is found and no other matches are accessed.
60 *
61 * @param lsMatcher the local search matcher that finished
62 * @since 2.0
63 */
64 default void noMoreMatchesAvailable(LocalSearchMatcher lsMatcher) {};
65
66 /**
67 * Callback method to indicate switching to a new plan during the execution of a pattern matching
68 *
69 * @param oldPlan the plan that is finished. Value is null when the first plan is starting.
70 * @param newPlan the plan that will begin execution
71 * @since 2.0
72 */
73 default void planChanged(Optional<SearchPlan> oldPlan, Optional<SearchPlan> newPlan) {};
74
75 /**
76 * Callback method to indicate the selection of an operation to execute
77 *
78 * @param plan the current plan executor
79 * @param frame the current matching frame
80 * @param isBacktrack if true, the selected operation was reached via backtracking
81 * @since 2.0
82 */
83 default void operationSelected(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isBacktrack) {};
84
85 /**
86 * Callback method to indicate that an operation is executed
87 *
88 * @param plan the current plan
89 * @param frame the current matching frame
90 * @param isSuccessful if true, the operation executed successfully, or false if the execution failed and backtracking will happen
91 * @since 2.0
92 */
93 default void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isSuccessful) {};
94
95 /**
96 * Callback that is used to indicate that a match has been found
97 *
98 * @param plan the search plan executor that found the match
99 * @param frame the frame that holds the substitutions of the variables that match
100 * @since 2.0
101 */
102 default void matchFound(SearchPlan plan, MatchingFrame frame) {};
103 /**
104 * Callback that is used to indicate that the previously reported match has been found as a duplicate, thus will be ignored from the match results.
105 *
106 * @param plan the search plan executor that found the match
107 * @param frame the frame that holds the substitutions of the variables that match
108 * @since 2.0
109 */
110 default void duplicateMatchFound(MatchingFrame frame) {};
111
112 /**
113 * Callback method to indicate that a search plan is initialized in an executor with the given frame and starting operation
114 *
115 * @param searchPlan
116 * @param frame
117 * @since 2.0
118 */
119 default void executorInitializing(SearchPlan searchPlan, MatchingFrame frame) {};
120}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java
new file mode 100644
index 00000000..71aa4aac
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java
@@ -0,0 +1,120 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import org.apache.log4j.Logger;
12import tools.refinery.viatra.runtime.localsearch.matcher.integration.IAdornmentProvider;
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
15import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
16import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.matchers.util.ICache;
19import tools.refinery.viatra.runtime.matchers.util.IProvider;
20
21import java.util.Collections;
22
23/**
24 * The {@link ISearchContext} interface allows search operations to reuse platform services such as the indexer.
25 *
26 * @author Zoltan Ujhelyi
27 * @noreference This interface is not intended to be referenced by clients.
28 * @noimplement This interface is not intended to be implemented by clients.
29 * @noextend This interface is not intended to be extended by clients.
30 *
31 */
32public interface ISearchContext {
33
34 /**
35 * Provides access to the generic query runtime context of the current engine
36 * @since 1.7
37 */
38 IQueryRuntimeContext getRuntimeContext();
39
40 /**
41 * Returns a matcher for a selected query specification.
42 *
43 * @throws ViatraQueryRuntimeException
44 * @since 1.5
45 */
46 IQueryResultProvider getMatcher(CallWithAdornment dependency);
47
48 /**
49 * Allows search operations to cache values through the entire lifecycle of the local search backend. The values are
50 * calculated if not cached before using the given provider, or returned from the cache accordingly.
51 *
52 * @since 1.7
53 */
54 <T> T accessBackendLevelCache(Object key, Class<? extends T> clazz, IProvider<T> valueProvider);
55
56 /**
57 * Returns the engine-specific logger
58 *
59 * @since 2.0
60 */
61 Logger getLogger();
62
63 /**
64 * @noreference This class is not intended to be referenced by clients.
65 * @noimplement This interface is not intended to be implemented by clients.
66 * @noextend This interface is not intended to be extended by clients.
67 */
68 public class SearchContext implements ISearchContext {
69
70 private final IQueryRuntimeContext runtimeContext;
71
72 private final ICache backendLevelCache;
73 private final Logger logger;
74 private final ResultProviderRequestor resultProviderRequestor;
75
76 /**
77 * Initializes a search context using an arbitrary backend context
78 */
79 public SearchContext(IQueryBackendContext backendContext, ICache backendLevelCache,
80 ResultProviderRequestor resultProviderRequestor) {
81 this.resultProviderRequestor = resultProviderRequestor;
82 this.runtimeContext = backendContext.getRuntimeContext();
83 this.logger = backendContext.getLogger();
84
85 this.backendLevelCache = backendLevelCache;
86 }
87
88 /**
89 * @throws ViatraQueryRuntimeException
90 * @since 2.1
91 */
92 @Override
93 public IQueryResultProvider getMatcher(CallWithAdornment dependency) {
94 // Inject adornment for referenced pattern
95 IAdornmentProvider adornmentProvider = query -> {
96 if (query.equals(dependency.getReferredQuery())){
97 return Collections.singleton(dependency.getAdornment());
98 }
99 return Collections.emptySet();
100 };
101 return resultProviderRequestor.requestResultProvider(dependency.getCall(),
102 IAdornmentProvider.toHint(adornmentProvider));
103 }
104
105 @Override
106 public <T> T accessBackendLevelCache(Object key, Class<? extends T> clazz, IProvider<T> valueProvider) {
107 return backendLevelCache.getValue(key, clazz, valueProvider);
108 }
109
110 public IQueryRuntimeContext getRuntimeContext() {
111 return runtimeContext;
112 }
113
114 @Override
115 public Logger getLogger() {
116 return logger;
117 }
118
119 }
120}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java
new file mode 100644
index 00000000..e31d7b5c
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java
@@ -0,0 +1,301 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import java.util.ArrayList;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.NoSuchElementException;
17import java.util.Objects;
18import java.util.Optional;
19import java.util.Set;
20import java.util.Spliterator;
21import java.util.Spliterators;
22import java.util.stream.Collectors;
23import java.util.stream.Stream;
24import java.util.stream.StreamSupport;
25
26import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
27import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor;
28import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
29import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanExecutor;
30import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
31import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
32import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
33import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
34import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
35import tools.refinery.viatra.runtime.matchers.util.Preconditions;
36
37/**
38 * @author Zoltan Ujhelyi
39 * @noinstantiate This class is not intended to be instantiated by clients.
40 */
41public final class LocalSearchMatcher implements ILocalSearchAdaptable {
42
43 private final List<SearchPlanExecutor> plan;
44 private final IPlanDescriptor planDescriptor;
45 private final List<ILocalSearchAdapter> adapters;
46
47 /**
48 * @since 2.0
49 */
50 public List<SearchPlanExecutor> getPlan() {
51 return plan;
52 }
53
54 @Override
55 public List<ILocalSearchAdapter> getAdapters() {
56 return new ArrayList<>(adapters);
57 }
58
59 private abstract class PlanExecutionIterator implements Iterator<Tuple> {
60
61 protected final Iterator<SearchPlanExecutor> planIterator;
62
63 protected SearchPlanExecutor currentPlan;
64 protected MatchingFrame frame;
65 protected final Set<ITuple> matchSet;
66 protected VolatileModifiableMaskedTuple parametersOfFrameView;
67 private boolean isNextMatchCalculated;
68
69 public PlanExecutionIterator(final Iterator<SearchPlanExecutor> planIterator) {
70 this.planIterator = planIterator;
71 isNextMatchCalculated = false;
72 matchSet = new HashSet<>();
73 }
74
75 protected boolean selectNextPlan() {
76 if(currentPlan != null) {
77 currentPlan.removeAdapters(adapters);
78 }
79 boolean validPlanSelected = false;
80
81 SearchPlanExecutor nextPlan = null;
82
83 while (!validPlanSelected && planIterator.hasNext()) {
84 nextPlan = planIterator.next();
85 nextPlan.addAdapters(adapters);
86 nextPlan.resetPlan();
87
88 validPlanSelected = initializeMatchingFrame(nextPlan);
89 }
90
91 if (validPlanSelected) {
92 for (ILocalSearchAdapter adapter : adapters) {
93 adapter.planChanged(Optional.ofNullable(currentPlan).map(SearchPlanExecutor::getSearchPlan),
94 Optional.ofNullable(nextPlan).map(SearchPlanExecutor::getSearchPlan));
95 }
96 currentPlan = nextPlan;
97 return true;
98 } else {
99 currentPlan = null;
100 return false;
101 }
102 }
103
104 protected abstract boolean initializeMatchingFrame(SearchPlanExecutor nextPlan);
105
106 private boolean findNextNewMatchInCurrentPlan() {
107 boolean foundMatch = currentPlan.execute(frame);
108 while (foundMatch && matchSet.contains(parametersOfFrameView)) {
109 for (ILocalSearchAdapter adapter : adapters) {
110 adapter.duplicateMatchFound(frame);
111 }
112 foundMatch = currentPlan.execute(frame);
113 }
114 return foundMatch;
115 }
116
117 @Override
118 public boolean hasNext() {
119 if (isNextMatchCalculated) {
120 return true;
121 }
122 if (currentPlan == null) {
123 return false;
124 }
125 boolean foundMatch = findNextNewMatchInCurrentPlan();
126
127 while (!foundMatch && planIterator.hasNext()) {
128 foundMatch = selectNextPlan() && findNextNewMatchInCurrentPlan();
129 }
130 if (!foundMatch) {
131 for (ILocalSearchAdapter adapter : adapters) {
132 adapter.noMoreMatchesAvailable(LocalSearchMatcher.this);
133 }
134 }
135 isNextMatchCalculated = foundMatch;
136 return foundMatch;
137 }
138
139 @Override
140 public Tuple next() {
141 if (!hasNext()) {
142 throw new NoSuchElementException("No more matches available.");
143 }
144 isNextMatchCalculated = false;
145 final Tuple match = parametersOfFrameView.toImmutable();
146 matchSet.add(match);
147 return match;
148 }
149 }
150
151 private class PlanExecutionIteratorWithArrayParameters extends PlanExecutionIterator {
152
153 private final Object[] parameterValues;
154
155 public PlanExecutionIteratorWithArrayParameters(Iterator<SearchPlanExecutor> planIterator, final Object[] parameterValues) {
156 super(planIterator);
157 this.parameterValues = parameterValues;
158 selectNextPlan();
159 }
160
161 protected boolean initializeMatchingFrame(SearchPlanExecutor nextPlan) {
162 frame = new MatchingFrame(nextPlan.getVariableMapping().size());
163 parametersOfFrameView = new VolatileModifiableMaskedTuple(frame, nextPlan.getParameterMask());
164 for (int i = 0; i < parameterValues.length; i++) {
165 Object valueToSet = parameterValues[i];
166 if (valueToSet != null) {
167 Object oldValue = parametersOfFrameView.get(i);
168 if (oldValue == null) {
169 parametersOfFrameView.set(i, valueToSet);
170 } else if (!Objects.equals(valueToSet, oldValue)) {
171 // Initial value setting resulted in contradictory values. This can happen because two parameter
172 // variables have been unified but the call provides different values for the parameters.
173 return false;
174 }
175 // If oldValue is not null but equal to newValue, the setting can be ignored
176 }
177 }
178
179 return true;
180 }
181 }
182 private class PlanExecutionIteratorWithTupleParameters extends PlanExecutionIterator {
183
184 private final ITuple parameterValues;
185 private final TupleMask parameterSeedMask;
186
187 public PlanExecutionIteratorWithTupleParameters(Iterator<SearchPlanExecutor> planIterator, final TupleMask parameterSeedMask, final ITuple parameterValues) {
188 super(planIterator);
189 this.parameterSeedMask = parameterSeedMask;
190 this.parameterValues = parameterValues;
191 selectNextPlan();
192 }
193
194 protected boolean initializeMatchingFrame(SearchPlanExecutor nextPlan) {
195 frame = new MatchingFrame(nextPlan.getVariableMapping().size());
196 parametersOfFrameView = new VolatileModifiableMaskedTuple(frame, nextPlan.getParameterMask());
197 for (int i = 0; i < parameterSeedMask.getSize(); i++) {
198 int index = parameterSeedMask.indices[i];
199 Object valueToSet = parameterValues.get(i);
200 if (valueToSet != null) {
201 Object oldValue = parametersOfFrameView.get(index);
202 if (oldValue == null) {
203 parametersOfFrameView.set(index, valueToSet);
204 } else if (!Objects.equals(valueToSet, oldValue)) {
205 // Initial value setting resulted in contradictory values. This can happen because two parameter
206 // variables have been unified but the call provides different values for the parameters.
207 return false;
208 }
209 // If oldValue is not null but equal to newValue, the setting can be ignored
210 }
211 }
212
213 return true;
214 }
215 }
216
217 /**
218 * @since 2.0
219 */
220 public LocalSearchMatcher(ISearchContext searchContext, IPlanDescriptor planDescriptor, List<SearchPlan> plan) {
221 Preconditions.checkArgument(planDescriptor != null, "Cannot initialize matcher with null query.");
222 this.planDescriptor = planDescriptor;
223 this.plan = plan.stream().map(p -> new SearchPlanExecutor(p, searchContext)).collect(Collectors.toList());
224 this.adapters = new LinkedList<>();
225 }
226
227 @Override
228 public void addAdapter(ILocalSearchAdapter adapter) {
229 this.adapters.add(adapter);
230 adapter.adapterRegistered(this);
231 }
232
233 @Override
234 public void removeAdapter(ILocalSearchAdapter adapter) {
235 this.adapters.remove(adapter);
236 adapter.adapterUnregistered(this);
237 }
238
239 @Override
240 public void addAdapters(List<ILocalSearchAdapter> adapters) {
241 this.adapters.addAll(adapters);
242 for (ILocalSearchAdapter adapter : adapters) {
243 adapter.adapterRegistered(this);
244 }
245 }
246
247 @Override
248 public void removeAdapters(List<ILocalSearchAdapter> adapters) {
249 this.adapters.removeAll(adapters);
250 for (ILocalSearchAdapter adapter : adapters) {
251 adapter.adapterUnregistered(this);
252 }
253 }
254
255 public int getParameterCount() {
256 return planDescriptor.getQuery().getParameters().size();
257 }
258
259 private void matchingStarted() {
260 for (ILocalSearchAdapter adapter : adapters) {
261 adapter.patternMatchingStarted(this);
262 }
263 }
264
265 /**
266 * @since 2.0
267 */
268 public Stream<Tuple> streamMatches(final Object[] parameterValues) {
269 matchingStarted();
270 PlanExecutionIterator it = new PlanExecutionIteratorWithArrayParameters(plan.iterator(), parameterValues);
271 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it,
272 Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT), false);
273 }
274
275 /**
276 * @since 2.0
277 */
278 public Stream<Tuple> streamMatches(TupleMask parameterSeedMask, final ITuple parameterValues) {
279 matchingStarted();
280 PlanExecutionIterator it = new PlanExecutionIteratorWithTupleParameters(
281 plan.iterator(), parameterSeedMask, parameterValues);
282 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it,
283 Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT), false);
284 }
285
286 /**
287 * Returns the query specification this matcher used as source for the implementation
288 * @return never null
289 */
290 public PQuery getQuerySpecification() {
291 return planDescriptor.getQuery();
292 }
293
294
295 /**
296 * @since 1.5
297 */
298 public IPlanDescriptor getPlanDescriptor() {
299 return planDescriptor;
300 }
301}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java
new file mode 100644
index 00000000..6bf25192
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java
@@ -0,0 +1,97 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher;
10
11import java.util.Set;
12
13import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
16
17public class MatcherReference {
18 final PQuery query;
19 final Set<PParameter> adornment;
20
21 /**
22 * Hints that can override the callee's own hints. This field is intentionally left out from hashCode and equals
23 */
24 final QueryEvaluationHint hints;
25
26 /**
27 * @since 1.4
28 */
29 public MatcherReference(PQuery query, Set<PParameter> adornment, QueryEvaluationHint hints) {
30 super();
31 this.query = query;
32 this.adornment = adornment;
33 this.hints = hints;
34 }
35
36 public MatcherReference(PQuery query, Set<PParameter> adornment){
37 this(query, adornment, null);
38 }
39
40 public PQuery getQuery() {
41 return query;
42 }
43 public Set<PParameter> getAdornment() {
44 return adornment;
45 }
46 @Override
47 public int hashCode() {
48 final int prime = 31;
49 int result = 1;
50
51 result = prime * result + ((adornment == null) ? 0 : adornment.hashCode());
52 result = prime * result + ((query == null) ? 0 : query.hashCode());
53 return result;
54 }
55 @Override
56 public boolean equals(Object obj) {
57 if (this == obj)
58 return true;
59 if (obj == null)
60 return false;
61 if (getClass() != obj.getClass())
62 return false;
63 MatcherReference other = (MatcherReference) obj;
64 if (adornment == null) {
65 if (other.adornment != null)
66 return false;
67 } else if (!adornment.equals(other.adornment))
68 return false;
69 if (query == null) {
70 if (other.query != null)
71 return false;
72 } else if (!query.equals(other.query))
73 return false;
74 return true;
75 }
76
77 /**
78 * @return the hints to override the called reference's own hints with. Can be null.
79 * @since 1.4
80 */
81 public QueryEvaluationHint getHints() {
82 return hints;
83 }
84
85 @Override
86 public String toString() {
87 StringBuilder sb = new StringBuilder();
88 sb.append(query.getFullyQualifiedName());
89 sb.append("(");
90 for(PParameter p : query.getParameters()){
91 sb.append(adornment.contains(p) ? "b" : "f");
92 }
93 sb.append(")");
94 return sb.toString();
95 }
96
97} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java
new file mode 100644
index 00000000..1ae24d2d
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java
@@ -0,0 +1,532 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.lang.reflect.InvocationTargetException;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.LinkedHashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Objects;
22import java.util.Optional;
23import java.util.Queue;
24import java.util.Set;
25import java.util.concurrent.Callable;
26import java.util.stream.Collectors;
27import java.util.stream.IntStream;
28import java.util.stream.Stream;
29
30import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException;
31import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
32import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
33import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher;
34import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
35import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor;
36import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider;
37import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
38import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody;
39import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
40import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
41import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
42import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
43import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
44import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
45import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
46import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption;
47import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
48import tools.refinery.viatra.runtime.matchers.context.IInputKey;
49import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
50import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
51import tools.refinery.viatra.runtime.matchers.context.IndexingService;
52import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
53import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper;
54import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
55import tools.refinery.viatra.runtime.matchers.psystem.PBody;
56import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
57import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
58import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries;
59import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
60import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
61import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
62import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
63import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
64import tools.refinery.viatra.runtime.matchers.util.Accuracy;
65
66/**
67 * @author Zoltan Ujhelyi
68 * @since 1.7
69 *
70 */
71public abstract class AbstractLocalSearchResultProvider implements IQueryResultProvider {
72
73 protected final LocalSearchBackend backend;
74 protected final IQueryBackendContext backendContext;
75 protected final IQueryRuntimeContext runtimeContext;
76 protected final PQuery query;
77 protected final QueryEvaluationHint userHints;
78 protected final Map<PQuery, LocalSearchHints> hintCache = new HashMap<>();
79 protected final IPlanProvider planProvider;
80 private static final String PLAN_CACHE_KEY = AbstractLocalSearchResultProvider.class.getName() + "#planCache";
81 private final Map<MatcherReference, IPlanDescriptor> planCache;
82 protected final ISearchContext searchContext;
83 /**
84 * @since 2.1
85 */
86 protected ResultProviderRequestor resultProviderRequestor;
87
88 /**
89 * @since 1.5
90 */
91 @SuppressWarnings({ "unchecked"})
92 public AbstractLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
93 IPlanProvider planProvider, QueryEvaluationHint userHints) {
94 this.backend = backend;
95 this.backendContext = context;
96 this.query = query;
97
98 this.planProvider = planProvider;
99 this.userHints = userHints;
100 this.runtimeContext = context.getRuntimeContext();
101 this.resultProviderRequestor = backend.getResultProviderRequestor(query, userHints);
102 this.searchContext = new ISearchContext.SearchContext(backendContext, backend.getCache(), resultProviderRequestor);
103 this.planCache = backend.getCache().getValue(PLAN_CACHE_KEY, Map.class, HashMap::new);
104 }
105
106 protected abstract IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, LocalSearchHints configuration);
107
108 private IQueryRuntimeContext getRuntimeContext() {
109 return backend.getRuntimeContext();
110 }
111
112 private LocalSearchMatcher createMatcher(IPlanDescriptor plan, final ISearchContext searchContext) {
113 List<SearchPlan> executors = plan.getPlan().stream()
114 .map(input -> new SearchPlan(input.getBody(), input.getCompiledOperations(), input.calculateParameterMask(),
115 input.getVariableKeys()))
116 .collect(Collectors.toList());
117 return new LocalSearchMatcher(searchContext, plan, executors);
118 }
119
120 private IPlanDescriptor getOrCreatePlan(MatcherReference key, IQueryBackendContext backendContext, IOperationCompiler compiler, LocalSearchHints configuration, IPlanProvider planProvider) {
121 if (planCache.containsKey(key)){
122 return planCache.get(key);
123 } else {
124 IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler,
125 resultProviderRequestor, configuration, key);
126 planCache.put(key, plan);
127 return plan;
128 }
129 }
130
131 private IPlanDescriptor getOrCreatePlan(MatcherReference key, IPlanProvider planProvider) {
132 if (planCache.containsKey(key)){
133 return planCache.get(key);
134 } else {
135 LocalSearchHints configuration = overrideDefaultHints(key.getQuery());
136 IOperationCompiler compiler = getOperationCompiler(backendContext, configuration);
137 IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler,
138 resultProviderRequestor, configuration, key);
139 planCache.put(key, plan);
140 return plan;
141 }
142 }
143
144 private LocalSearchHints overrideDefaultHints(PQuery pQuery) {
145 if (hintCache.containsKey(pQuery)) {
146 return hintCache.get(pQuery);
147 } else {
148 LocalSearchHints hint = LocalSearchHints.getDefaultOverriddenBy(
149 computeOverridingHints(pQuery));
150 hintCache.put(pQuery, hint);
151 return hint;
152 }
153 }
154
155 /**
156 * Combine with {@link QueryHintOption#getValueOrDefault(QueryEvaluationHint)} to access
157 * hint settings not covered by {@link LocalSearchHints}
158 */
159 private QueryEvaluationHint computeOverridingHints(PQuery pQuery) {
160 return backendContext.getHintProvider().getQueryEvaluationHint(pQuery).overrideBy(userHints);
161 }
162
163 /**
164 * Prepare this result provider. This phase is separated from the constructor to allow the backend to cache its instance before
165 * requesting preparation for its dependencies.
166 * @since 1.5
167 */
168 public void prepare() {
169 try {
170 runtimeContext.coalesceTraversals(() -> {
171 LocalSearchHints configuration = overrideDefaultHints(query);
172 if (configuration.isUseBase()) {
173 indexInitializationBeforePlanning();
174 }
175 prepareDirectDependencies();
176 runtimeContext.executeAfterTraversal(AbstractLocalSearchResultProvider.this::preparePlansForExpectedAdornments);
177 return null;
178 });
179 } catch (InvocationTargetException e) {
180 throw new QueryProcessingException("Error while building required indexes: {1}", new String[]{e.getTargetException().getMessage()}, "Error while building required indexes.", query, e);
181 }
182 }
183
184 protected void preparePlansForExpectedAdornments() {
185 // Plan for possible adornments
186 for (Set<PParameter> adornment : overrideDefaultHints(query).getAdornmentProvider().getAdornments(query)) {
187 MatcherReference reference = new MatcherReference(query, adornment, userHints);
188 LocalSearchHints configuration = overrideDefaultHints(query);
189 IOperationCompiler compiler = getOperationCompiler(backendContext, configuration);
190 IPlanDescriptor plan = getOrCreatePlan(reference, backendContext, compiler, configuration, planProvider);
191 // Index keys
192 try {
193 if (configuration.isUseBase()) {
194 indexKeys(plan.getIteratedKeys());
195 }
196 } catch (InvocationTargetException e) {
197 throw new QueryProcessingException(e.getMessage(), null, e.getMessage(), query, e);
198 }
199 //Prepare dependencies
200 for(SearchPlanForBody body: plan.getPlan()){
201 for(CallWithAdornment dependency : body.getDependencies()){
202 searchContext.getMatcher(dependency);
203 }
204 }
205 }
206 }
207
208 protected void prepareDirectDependencies() {
209 // Do not prepare for any adornment at this point
210 IAdornmentProvider adornmentProvider = input -> Collections.emptySet();
211 QueryEvaluationHint adornmentHint = IAdornmentProvider.toHint(adornmentProvider);
212
213 for(IQueryReference call : getDirectDependencies()){
214 resultProviderRequestor.requestResultProvider(call, adornmentHint);
215 }
216 }
217
218 /**
219 * This method is called before planning start to allow indexing. It is important to note that this method is called
220 * inside a coalesceTraversals block, meaning (1) it is safe to add multiple registration requests as necessary, but
221 * (2) no value or statistics is available from the index.
222 *
223 * @throws ViatraQueryRuntimeException
224 */
225 protected void indexInitializationBeforePlanning() {
226 // By default, no indexing is necessary
227 }
228
229 /**
230 * Collects and indexes all types _directly_ referred by the PQuery {@link #query}. Types indirect
231 * @param requiredIndexingServices
232 */
233 protected void indexReferredTypesOfQuery(PQuery query, IndexingService requiredIndexingServices) {
234 PQueries.directlyRequiredTypesOfQuery(query, true /*only enumerables are considered for indexing */).forEach(
235 inputKey -> runtimeContext.ensureIndexed(inputKey, requiredIndexingServices)
236 );
237 }
238
239 private Set<IQueryReference> getDirectDependencies() {
240 IFlattenCallPredicate flattenPredicate = overrideDefaultHints(query).getFlattenCallPredicate();
241 Queue<PQuery> queue = new LinkedList<>();
242 Set<PQuery> visited = new HashSet<>();
243 Set<IQueryReference> result = new HashSet<>();
244 queue.add(query);
245
246 while(!queue.isEmpty()){
247 PQuery next = queue.poll();
248 visited.add(next);
249 for(PBody body : next.getDisjunctBodies().getBodies()){
250 for (IQueryReference call : body.getConstraintsOfType(IQueryReference.class)) {
251 if (call instanceof PositivePatternCall &&
252 flattenPredicate.shouldFlatten((PositivePatternCall) call))
253 {
254 PQuery dep = ((PositivePatternCall) call).getReferredQuery();
255 if (!visited.contains(dep)){
256 queue.add(dep);
257 }
258 } else {
259 result.add(call);
260 }
261 }
262 }
263 }
264 return result;
265 }
266
267 private LocalSearchMatcher initializeMatcher(Object[] parameters) {
268 return newLocalSearchMatcher(parameters);
269 }
270
271 private LocalSearchMatcher initializeMatcher(TupleMask parameterSeedMask) {
272 return newLocalSearchMatcher(parameterSeedMask.transformUnique(query.getParameters()));
273
274 }
275
276
277 /**
278 * @throws ViatraQueryRuntimeException
279 */
280 public LocalSearchMatcher newLocalSearchMatcher(ITuple parameters) {
281 final Set<PParameter> adornment = new HashSet<>();
282 for (int i = 0; i < parameters.getSize(); i++) {
283 if (parameters.get(i) != null) {
284 adornment.add(query.getParameters().get(i));
285 }
286 }
287
288 return newLocalSearchMatcher(adornment);
289 }
290
291 /**
292 * @throws ViatraQueryRuntimeException
293 */
294 public LocalSearchMatcher newLocalSearchMatcher(Object[] parameters) {
295 final Set<PParameter> adornment = new HashSet<>();
296 for (int i = 0; i < parameters.length; i++) {
297 if (parameters[i] != null) {
298 adornment.add(query.getParameters().get(i));
299 }
300 }
301
302 return newLocalSearchMatcher(adornment);
303 }
304
305 private LocalSearchMatcher newLocalSearchMatcher(final Set<PParameter> adornment) {
306 final MatcherReference reference = new MatcherReference(query, adornment, userHints);
307
308 IPlanDescriptor plan = getOrCreatePlan(reference, planProvider);
309 if (overrideDefaultHints(reference.getQuery()).isUseBase()){
310 try {
311 indexKeys(plan.getIteratedKeys());
312 } catch (InvocationTargetException e) {
313 throw new LocalSearchException("Could not index keys", e);
314 }
315 }
316
317 LocalSearchMatcher matcher = createMatcher(plan, searchContext);
318 matcher.addAdapters(backend.getAdapters());
319 return matcher;
320 }
321
322 private void indexKeys(final Iterable<IInputKey> keys) throws InvocationTargetException {
323 final IQueryRuntimeContext qrc = getRuntimeContext();
324 qrc.coalesceTraversals(new Callable<Void>() {
325
326 @Override
327 public Void call() throws Exception {
328 for(IInputKey key : keys){
329 if (key.isEnumerable()) {
330 qrc.ensureIndexed(key, IndexingService.INSTANCES);
331 }
332 }
333 return null;
334 }
335 });
336 }
337
338 @Override
339 public boolean hasMatch(Object[] parameters) {
340 final LocalSearchMatcher matcher = initializeMatcher(parameters);
341 return matcher.streamMatches(parameters).findAny().isPresent();
342 }
343
344 @Override
345 public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) {
346 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
347 return matcher.streamMatches(parameterSeedMask, parameters).findAny().isPresent();
348 }
349
350 @Override
351 public Optional<Tuple> getOneArbitraryMatch(Object[] parameters) {
352 final LocalSearchMatcher matcher = initializeMatcher(parameters);
353 return matcher.streamMatches(parameters).findAny();
354 }
355
356 @Override
357 public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) {
358 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
359 return matcher.streamMatches(parameterSeedMask, parameters).findAny();
360 }
361
362 @Override
363 public int countMatches(Object[] parameters) {
364 final LocalSearchMatcher matcher = initializeMatcher(parameters);
365 // Count returns long; casting to int - in case of integer overflow casting will throw the exception
366 return (int) matcher.streamMatches(parameters).count();
367 }
368
369 @Override
370 public int countMatches(TupleMask parameterSeedMask, ITuple parameters) {
371 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
372 // Count returns long; casting to int - in case of integer overflow casting will throw the exception
373 return (int) matcher.streamMatches(parameterSeedMask, parameters).count();
374 }
375
376 private static final double ESTIMATE_CEILING = Long.MAX_VALUE / 16.0;
377
378 @Override
379 public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) {
380 if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types
381 final List<PParameter> parameters = query.getParameters();
382 final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer()
383 .getProjectedFunctionalDependencies(query, false);
384
385 List<Integer> projectionIndices = groupMask.getIndicesAsList();
386
387 return estimateParameterCombinations(requiredAccuracy, parameters, dependencies,
388 projectionIndices,
389 Collections.emptySet() /* No parameters with fixed value */).map(Double::longValue);
390 }
391 else return Optional.empty();
392 }
393
394 @Override
395 public Optional<Double> estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) {
396 if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types
397 final List<PParameter> parameters = query.getParameters();
398 final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer()
399 .getProjectedFunctionalDependencies(query, false);
400
401 // all parameters used for the estimation - determinized order
402 final List<Integer> allParameterIndices =
403 IntStream.range(0, parameters.size()).boxed().collect(Collectors.toList());
404
405 // some free parameters are functionally determined by bound parameters
406 final Set<Integer> boundOrImplied = FunctionalDependencyHelper.closureOf(groupMask.getIndicesAsList(),
407 dependencies);
408
409 return estimateParameterCombinations(requiredAccuracy, parameters, dependencies,
410 allParameterIndices,
411 boundOrImplied);
412 }
413 else return Optional.empty();
414 }
415
416 /**
417 * @since 2.1
418 * @noreference This method is not intended to be referenced by clients.
419 */
420 public double estimateCost(TupleMask inputBindingMask) {
421 // TODO this is currently an abstract cost, not really a branching factor
422
423 HashSet<PParameter> adornment = new HashSet<>(inputBindingMask.transform(query.getParameters()));
424 final MatcherReference reference = new MatcherReference(query, adornment, userHints);
425 IPlanDescriptor plan = getOrCreatePlan(reference, planProvider);
426
427 return plan.getPlan().stream().mapToDouble(SearchPlanForBody::getCost).sum();
428 }
429
430 /**
431 * Approximates using parameter types
432 */
433 private Optional<Double> estimateParameterCombinations(
434 Accuracy requiredAccuracy,
435 final List<PParameter> parameters,
436 final Map<Set<Integer>, Set<Integer>> functionalDependencies,
437 final Collection<Integer> parameterIndicesToEstimate,
438 final Set<Integer> otherDeterminingIndices)
439 {
440 // keep order deterministic
441 LinkedHashSet<Integer> freeParameterIndices = new LinkedHashSet<>(parameterIndicesToEstimate);
442
443 // determining indices are bound
444 freeParameterIndices.removeAll(otherDeterminingIndices);
445
446 // some free parameters are functionally determined by other free parameters
447 for (Integer candidateForRemoval : new ArrayList<>(freeParameterIndices)) {
448 List<Integer> others = Stream.concat(
449 otherDeterminingIndices.stream(),
450 freeParameterIndices.stream().filter(index -> !Objects.equals(index, candidateForRemoval))
451 ).collect(Collectors.toList());
452 Set<Integer> othersClosure = FunctionalDependencyHelper.closureOf(others, functionalDependencies);
453 if (othersClosure.contains(candidateForRemoval)) {
454 // other parameters functionally determine this mone, does not count towards estimate
455 freeParameterIndices.remove(candidateForRemoval);
456 }
457 }
458
459
460 Optional<Double> result = Optional.of(1.0);
461 // TODO this is currently works with declared types only. For better results, information from
462 // the Type inferrer should be included in the PSystem
463 for (int i = 0; (i < parameters.size()); i++) {
464 final IInputKey type = parameters.get(i).getDeclaredUnaryType();
465 if (freeParameterIndices.contains(i) && type != null) {
466 result = result.flatMap(accumulator ->
467 runtimeContext.estimateCardinality(type, TupleMask.identity(1), requiredAccuracy).map(multiplier ->
468 Math.min(accumulator * multiplier, ESTIMATE_CEILING /* avoid overflow */)
469 ));
470 }
471 }
472 // TODO better approximate cardinality based on plan, branching factors, etc.
473 return result;
474 }
475
476
477 @Override
478 public Stream<Tuple> getAllMatches(Object[] parameters) {
479 final LocalSearchMatcher matcher = initializeMatcher(parameters);
480 return matcher.streamMatches(parameters);
481 }
482
483 @Override
484 public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters) {
485 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
486 return matcher.streamMatches(parameterSeedMask, parameters);
487 }
488
489 @Override
490 public IQueryBackend getQueryBackend() {
491 return backend;
492 }
493
494 @Override
495 public void addUpdateListener(IUpdateable listener, Object listenerTag, boolean fireNow) {
496 // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED);
497 }
498
499 @Override
500 public void removeUpdateListener(Object listenerTag) {
501 // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED);
502 }
503
504 /**
505 * @since 1.4
506 */
507 public IMatcherCapability getCapabilites() {
508 LocalSearchHints configuration = overrideDefaultHints(query);
509 return configuration;
510 }
511
512 /**
513 * Forgets all stored plans in this result provider. If no plans are stored, nothing happens.
514 *
515 * @since 2.0
516 * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}.
517 */
518 public void forgetAllPlans() {
519 planCache.clear();
520 }
521
522 /**
523 * Returns a search plan for a given adornment if exists
524 *
525 * @return a search plan for the pattern with the given adornment, or null if none exists
526 * @since 2.0
527 * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}.
528 */
529 public IPlanDescriptor getSearchPlan(Set<PParameter> adornment) {
530 return planCache.get(new MatcherReference(query, adornment));
531 }
532} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java
new file mode 100644
index 00000000..f801163e
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.util.Set;
12import java.util.stream.Collectors;
13
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18import tools.refinery.viatra.runtime.matchers.util.Sets;
19
20
21/**
22 * This implementation calculates all valid adornments for the given query, respecting the parameter direction constraints.
23 *
24 * @author Grill Balázs
25 * @since 1.5
26 */
27public class AllValidAdornments implements IAdornmentProvider {
28
29 @Override
30 public Iterable<Set<PParameter>> getAdornments(PQuery query) {
31 final Set<PParameter> ins = query.getParameters().stream().filter(PQueries.parameterDirectionPredicate(PParameterDirection.IN)).collect(Collectors.toSet());
32 Set<PParameter> inouts = query.getParameters().stream().filter(PQueries.parameterDirectionPredicate(PParameterDirection.INOUT)).collect(Collectors.toSet());
33 Set<? extends Set<PParameter>> possibleInouts = Sets.powerSet(inouts);
34 return possibleInouts.stream().map(input -> Sets.union(ins, input)).collect(Collectors.toSet());
35 }
36
37}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java
new file mode 100644
index 00000000..bf1b61b5
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
12import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
13
14/**
15 * Forbids flattening of patterns that have more than one body.
16 *
17 * @since 2.1
18
19 * @author Gabor Bergmann
20 *
21 */
22public class DontFlattenDisjunctive implements IFlattenCallPredicate {
23
24 @Override
25 public boolean shouldFlatten(PositivePatternCall positivePatternCall) {
26 return 1 >= positivePatternCall.getReferredQuery().getDisjunctBodies().getBodies().size();
27 }
28
29}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java
new file mode 100644
index 00000000..1b918528
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
12import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint.BackendRequirement;
13import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
14import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
15
16/**
17 * This implementation forbids flattening of patterns marked to be executed with a caching / incremental backend.
18 * This makes is possible for the user to configure hybrid matching via using
19 * the 'search' and 'incremental keywords in the pattern definition file.
20 *
21 * @since 1.5
22 *
23 */
24public class DontFlattenIncrementalPredicate implements IFlattenCallPredicate {
25
26 @Override
27 public boolean shouldFlatten(PositivePatternCall positivePatternCall) {
28 QueryEvaluationHint evaluationHints = positivePatternCall.getReferredQuery().getEvaluationHints();
29 if (evaluationHints == null) return true;
30
31 BackendRequirement backendRequirementType = evaluationHints.getQueryBackendRequirementType();
32 switch(backendRequirementType) {
33 case DEFAULT_CACHING:
34 return false;
35 case SPECIFIC:
36 return !evaluationHints.getQueryBackendFactory().isCaching();
37 case UNSPECIFIED:
38 case DEFAULT_SEARCH:
39 default:
40 return true;
41 }
42 }
43
44}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java
new file mode 100644
index 00000000..ed6c1e5f
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider;
12import tools.refinery.viatra.runtime.localsearch.planner.compiler.GenericOperationCompiler;
13import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
14import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
15import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
16import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
17import tools.refinery.viatra.runtime.matchers.context.IndexingService;
18import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
19
20/**
21 * @author Zoltan Ujhelyi
22 * @since 1.7
23 *
24 */
25public class GenericLocalSearchResultProvider extends AbstractLocalSearchResultProvider {
26
27 /**
28 * @throws ViatraQueryRuntimeException
29 */
30 public GenericLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
31 IPlanProvider planProvider, QueryEvaluationHint userHints) {
32 super(backend, context, query, planProvider, userHints);
33 }
34
35 @Override
36 protected void indexInitializationBeforePlanning() {
37 super.indexInitializationBeforePlanning();
38
39 indexReferredTypesOfQuery(query, IndexingService.INSTANCES);
40 indexReferredTypesOfQuery(query, IndexingService.STATISTICS);
41 }
42
43 @Override
44 protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext,
45 LocalSearchHints configuration) {
46 return new GenericOperationCompiler(runtimeContext);
47 }
48
49}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java
new file mode 100644
index 00000000..86058be0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java
@@ -0,0 +1,72 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.util.Collections;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
15import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint.BackendRequirement;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18
19/**
20 * An adornment provider is used to define the adornments the pattern matcher should prepare for.
21 *
22 * <p>A default implementation is available in {@link AllValidAdornments} that describes all
23 * adornments fulfilling the parameter direction declarations;
24 * another default option (with better performance but restricted applicability) is {@link LazyPlanningAdornments}.
25 *
26 * <br><br>
27 *
28 * Users may implement this interface to limit the number of prepared plans based on some runtime information:
29 *
30 * <pre>
31 * class SomeAdornments{
32 *
33 * public Iterable&lt;Set&lt;{@link PParameter}>> getAdornments({@link PQuery} query){
34 * if (SomeGeneratedQuerySpecification.instance().getInternalQueryRepresentation().equals(query)){
35 * return Collections.singleton(Sets.filter(Sets.newHashSet(query.getParameters()), new Predicate<PParameter>() {
36 *
37 * &#64;Override
38 * public boolean apply(PParameter input) {
39 * // Decide whether this particular parameter will be bound
40 * return false;
41 * }
42 * }));
43 * }
44 * // Returning an empty iterable is safe for unknown queries
45 * return Collections.emptySet();
46 * }
47 *
48 * }
49 * </pre>
50 *
51 * @author Grill Balázs
52 * @since 1.5
53 *
54 */
55public interface IAdornmentProvider {
56
57 /**
58 * The bound parameter sets
59 */
60 public Iterable<Set<PParameter>> getAdornments(PQuery query);
61
62 /**
63 * @return a simple hint that only overrides the adornment provider
64 * @since 2.1
65 */
66 public static QueryEvaluationHint toHint(IAdornmentProvider adornmentProvider) {
67 return new QueryEvaluationHint(
68 Collections.singletonMap(LocalSearchHintOptions.ADORNMENT_PROVIDER, adornmentProvider),
69 BackendRequirement.UNSPECIFIED);
70 }
71
72}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java
new file mode 100644
index 00000000..30b3689f
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.util.Collections;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
16
17/**
18 * This adornment provider does not trigger the preparation of any plans.
19 * Actual query plans will be computed on demand, when the first actual match request is made with a given adornment.
20 *
21 * <p> Caution: this is a safe default adornment provider for {@link GenericLocalSearchResultProvider} only;
22 * do not use for the EMF-specific LS backend.
23 *
24 * <p> The benefits is in execution time: query planning costs for adornments are postponed until first usage
25 * or even entirely avoided (when adornment is never used in practice).
26 * However, query evaluation time may become less predictable, as the first matcher call (with a given adornment)
27 * will include the planning cost.
28 * For benchmarking or other purposes where this is not desirable, use an adornment provider that demands plan precomputation for all necessary adornments.
29 *
30 * @author Gabor Bergmann
31 * @since 2.1
32 *
33 */
34public class LazyPlanningAdornments implements IAdornmentProvider {
35
36 @Override
37 public Iterable<Set<PParameter>> getAdornments(PQuery query) {
38 return Collections.emptySet();
39 }
40
41}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java
new file mode 100644
index 00000000..ae51e2b0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java
@@ -0,0 +1,259 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.lang.reflect.InvocationTargetException;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20import java.util.stream.Stream;
21
22import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException;
23import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter;
24import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor;
25import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider;
26import tools.refinery.viatra.runtime.localsearch.plan.SimplePlanProvider;
27import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
28import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
29import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
30import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
31import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
32import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
33import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
34import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
35import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
36import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
37import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
38import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
39import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
40import tools.refinery.viatra.runtime.matchers.util.ICache;
41import tools.refinery.viatra.runtime.matchers.util.PurgableCache;
42
43/**
44 * @author Marton Bur, Zoltan Ujhelyi
45 * @noextend This class is not intended to be subclassed by clients.
46 */
47public abstract class LocalSearchBackend implements IQueryBackend {
48
49 IQueryBackendContext context;
50 IPlanProvider planProvider;
51 private final Set<ILocalSearchAdapter> adapters = new HashSet<>();
52
53 private final PurgableCache generalCache;
54
55 private final Map<PQuery, List<AbstractLocalSearchResultProvider>> resultProviderCache = CollectionsFactory.createMap();
56
57
58 /**
59 * @since 1.5
60 */
61 public LocalSearchBackend(IQueryBackendContext context) {
62 super();
63 this.context = context;
64 this.generalCache = new PurgableCache();
65 this.planProvider = new SimplePlanProvider(context.getLogger());
66 }
67
68 @Override
69 public void flushUpdates() {
70
71 }
72
73 @Override
74 public IQueryResultProvider getResultProvider(PQuery query) {
75 return getResultProvider(query, null);
76 }
77
78 /**
79 * @since 1.4
80 */
81 @Override
82 public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) {
83
84 final QueryEvaluationHint callHints = getHintProvider().getQueryEvaluationHint(query).overrideBy(hints);
85 IMatcherCapability requestedCapability = context.getRequiredMatcherCapability(query, callHints);
86 for(AbstractLocalSearchResultProvider existingResultProvider : resultProviderCache.getOrDefault(query, Collections.emptyList())){
87 if (requestedCapability.canBeSubstitute(existingResultProvider.getCapabilites())){
88 return existingResultProvider;
89 }
90 }
91
92 AbstractLocalSearchResultProvider resultProvider = initializeResultProvider(query, hints);
93 resultProviderCache.computeIfAbsent(query, k->new ArrayList<>()).add(resultProvider);
94 resultProvider.prepare();
95 return resultProvider;
96 }
97
98 /**
99 * Returns a requestor that this backend uses while processing pattern calls <i>from</i> this query.
100 * @noreference This method is not intended to be referenced by clients.
101 * @since 2.1
102 */
103 public ResultProviderRequestor getResultProviderRequestor(PQuery query, QueryEvaluationHint userHints) {
104 QueryEvaluationHint hintOnQuery =
105 context.getHintProvider().getQueryEvaluationHint(query).overrideBy(userHints);
106 LocalSearchHints defaultsApplied = LocalSearchHints.getDefaultOverriddenBy(hintOnQuery);
107
108 return new ResultProviderRequestor(this,
109 context.getResultProviderAccess(),
110 context.getHintProvider(),
111 defaultsApplied.getCallDelegationStrategy(),
112 userHints,
113 /* no global overrides */ null);
114 }
115
116 /**
117 * @throws ViatraQueryRuntimeException
118 * @since 1.7
119 */
120 protected abstract AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, QueryEvaluationHint hints);
121
122 @Override
123 public void dispose() {
124 resultProviderCache.clear();
125 generalCache.purge();
126 }
127
128 @Override
129 public boolean isCaching() {
130 return false;
131 }
132
133 /**
134 * @since 2.0
135 */
136 @Override
137 public AbstractLocalSearchResultProvider peekExistingResultProvider(PQuery query) {
138 return resultProviderCache.getOrDefault(query, Collections.emptyList()).stream().findAny().orElse(null);
139 }
140
141 /**
142 * @since 1.4
143 */
144 public IQueryRuntimeContext getRuntimeContext() {
145 return context.getRuntimeContext();
146 }
147
148
149 /**
150 * @since 1.5
151 */
152 public QueryAnalyzer getQueryAnalyzer() {
153 return context.getQueryAnalyzer();
154 }
155
156
157 /**
158 * @since 1.4
159 */
160 public IQueryBackendHintProvider getHintProvider() {
161 return context.getHintProvider();
162 }
163
164 /**
165 * @since 1.5
166 */
167 public void addAdapter(ILocalSearchAdapter adapter){
168 adapters.add(adapter);
169 }
170
171 /**
172 * @since 1.5
173 */
174 public void removeAdapter(ILocalSearchAdapter adapter){
175 adapters.remove(adapter);
176 }
177
178 /**
179 * Return a copy of the current adapters
180 * @since 1.7
181 */
182 public List<ILocalSearchAdapter> getAdapters() {
183 return new ArrayList<>(adapters);
184 }
185
186 /**
187 * @since 1.5
188 */
189 public IQueryBackendContext getBackendContext() {
190 return context;
191 }
192
193 /**
194 * Returns the internal cache of the backend
195 * @since 1.7
196 * @noreference This method is not intended to be referenced by clients.
197 */
198 public ICache getCache() {
199 return generalCache;
200 }
201
202 /**
203 * Updates the previously stored search plans for one or more given queries, computing a new set of plans if
204 * necessary. The new plans created are the same that would be created by executing prepare on the given query
205 * definitions.
206 *
207 * @since 2.0
208 */
209 public void recomputePlans(PQuery... queries) {
210 recomputePlans(Arrays.stream(queries).flatMap(query -> resultProviderCache.getOrDefault(query, Collections.emptyList()).stream()));
211 }
212
213 /**
214 * Updates the previously stored search plans for one or more given queries, computing a new set of plans if
215 * necessary The new plans created are the same that would be created by executing prepare on the given query
216 * definitions.
217 *
218 * @since 2.0
219 */
220 public void recomputePlans(Collection<PQuery> queries) {
221 recomputePlans(queries.stream().flatMap(query -> resultProviderCache.getOrDefault(query, Collections.emptyList()).stream()));
222 }
223
224 /**
225 * Updates the previously stored search plans for one or more given queries, computing a new set of plans if
226 * necessary The new plans created are the same that would be created by executing prepare on the given query
227 * definitions.
228 *
229 * @since 2.0
230 */
231 public void recomputePlans() {
232 recomputePlans(resultProviderCache.values().stream().flatMap(List::stream));
233 }
234
235 private void recomputePlans(Stream<AbstractLocalSearchResultProvider> resultProviders) {
236 try {
237 context.getRuntimeContext().coalesceTraversals(() -> {
238 resultProviders.forEach(resultProvider -> {
239 resultProvider.forgetAllPlans();
240 resultProvider.prepare();
241 });
242 return null;
243 });
244 } catch (InvocationTargetException e) {
245 throw new LocalSearchException("Error while rebuilding plans: " + e.getMessage(), e);
246 }
247 }
248
249 /**
250 * Returns a search plan for a given query and adornment if such plan is already calculated.
251 *
252 * @return a previously calculated search plan for the given query and adornment, or null if no such plan exists
253 * @since 2.0
254 */
255 public IPlanDescriptor getSearchPlan(PQuery query, Set<PParameter> adornment) {
256 final AbstractLocalSearchResultProvider resultProvider = peekExistingResultProvider(query);
257 return (resultProvider == null) ? null : resultProvider.getSearchPlan(adornment);
258 }
259}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java
new file mode 100644
index 00000000..1dd08f98
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
13import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
14import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
15import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17
18/**
19 * @author Marton Bur, Zoltan Ujhelyi
20 * @since 1.7
21 *
22 */
23public enum LocalSearchGenericBackendFactory implements IQueryBackendFactory {
24
25 INSTANCE;
26
27 /**
28 * @since 1.5
29 */
30 @Override
31 public IQueryBackend create(IQueryBackendContext context) {
32 return new LocalSearchBackend(context) {
33
34 @Override
35 protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, QueryEvaluationHint hints) {
36 return new GenericLocalSearchResultProvider(this, context, query, planProvider, hints);
37 }
38
39 @Override
40 public IQueryBackendFactory getFactory() {
41 return INSTANCE;
42 }
43
44 };
45 }
46
47 @Override
48 public Class<? extends IQueryBackend> getBackendClass() {
49 return LocalSearchBackend.class;
50 }
51
52 /**
53 * @since 1.4
54 */
55 @Override
56 public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) {
57 return LocalSearchHints.parse(hint);
58 }
59
60 @Override
61 public boolean isCaching() {
62 return false;
63 }
64
65}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java
new file mode 100644
index 00000000..ea422d91
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider;
13
14/**
15 * @since 2.0
16 */
17public class LocalSearchGenericBackendFactoryProvider implements IQueryBackendFactoryProvider {
18
19 @Override
20 public IQueryBackendFactory getFactory() {
21 return LocalSearchGenericBackendFactory.INSTANCE;
22 }
23
24} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java
new file mode 100644
index 00000000..43462204
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction;
12import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.IndexerBasedConstraintCostFunction;
13import tools.refinery.viatra.runtime.matchers.backend.ICallDelegationStrategy;
14import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption;
15import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
16
17/**
18 *
19 * @author Gabor Bergmann
20 * @since 1.5
21 * @noinstantiate This class is not intended to be instantiated by clients.
22 */
23public final class LocalSearchHintOptions {
24
25 private LocalSearchHintOptions() {
26 // Private constructor for utility class
27 }
28
29 public static final QueryHintOption<Boolean> USE_BASE_INDEX =
30 hintOption("USE_BASE_INDEX", true);
31
32 // This key can be used to influence the core planner algorithm
33 public static final QueryHintOption<Integer> PLANNER_TABLE_ROW_COUNT =
34 hintOption("PLANNER_TABLE_ROW_COUNT", 4);
35 /**
36 * Cost function to be used by the planner. Must implement {@link ICostFunction}
37 * @since 1.4
38 */
39 public static final QueryHintOption<ICostFunction> PLANNER_COST_FUNCTION =
40 hintOption("PLANNER_COST_FUNCTION", new IndexerBasedConstraintCostFunction());
41 /**
42 * Predicate to decide whether to flatten specific positive pattern calls {@link IFlattenCallPredicate}
43 * @since 1.4
44 */
45 public static final QueryHintOption<IFlattenCallPredicate> FLATTEN_CALL_PREDICATE =
46 hintOption("FLATTEN_CALL_PREDICATE", new DontFlattenDisjunctive());
47 /**
48 * Strategy to decide how hints (most importantly, backend selection) propagate across pattern calls.
49 * Must implement {@link ICallDelegationStrategy}.
50 * @since 2.1
51 */
52 public static final QueryHintOption<ICallDelegationStrategy> CALL_DELEGATION_STRATEGY =
53 hintOption("CALL_DELEGATION_STRATEGY", ICallDelegationStrategy.FULL_BACKEND_ADHESION);
54
55 /**
56 * A provider of expected adornments {@link IAdornmentProvider}.
57 *
58 * The safe default is {@link AllValidAdornments};
59 * however, the generic backend variant may safely use {@link LazyPlanningAdornments} instead.
60 *
61 * @since 1.5
62 */
63 public static final QueryHintOption<IAdornmentProvider> ADORNMENT_PROVIDER =
64 hintOption("ADORNMENT_PROVIDER", new AllValidAdornments());
65
66 // internal helper for conciseness
67 private static <T, V extends T> QueryHintOption<T> hintOption(String hintKeyLocalName, V defaultValue) {
68 return new QueryHintOption<>(LocalSearchHintOptions.class, hintKeyLocalName, defaultValue);
69 }
70}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java
new file mode 100644
index 00000000..5f3895be
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java
@@ -0,0 +1,306 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction;
12import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.IndexerBasedConstraintCostFunction;
13import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
14import tools.refinery.viatra.runtime.matchers.backend.*;
15import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
16import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IRewriterTraceCollector;
17import tools.refinery.viatra.runtime.matchers.psystem.rewriters.NopTraceCollector;
18
19import java.util.HashMap;
20import java.util.Map;
21import java.util.Objects;
22
23import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.*;
24import static tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions.normalizationTraceCollector;
25
26/**
27 * Type safe builder and extractor for Local search specific hints
28 *
29 * @author Grill Balázs
30 * @since 1.4
31 *
32 */
33public final class LocalSearchHints implements IMatcherCapability {
34
35 private Boolean useBase = null;
36
37 private Integer rowCount = null;
38
39 private ICostFunction costFunction = null;
40
41 private IFlattenCallPredicate flattenCallPredicate = null;
42
43 private ICallDelegationStrategy callDelegationStrategy = null;
44
45 private IAdornmentProvider adornmentProvider = null;
46
47 private IRewriterTraceCollector traceCollector = NopTraceCollector.INSTANCE;
48
49 private IQueryBackendFactory backendFactory = null;
50
51 private LocalSearchHints() {}
52
53 /**
54 * Return the default settings overridden by the given hints
55 */
56 public static LocalSearchHints getDefaultOverriddenBy(QueryEvaluationHint overridingHint){
57 return parse(getDefault().build(overridingHint));
58 }
59
60 /**
61 * Default settings which are considered the most safe, providing a reasonable performance for most of the cases. Assumes the availability of the base indexer.
62 */
63 public static LocalSearchHints getDefault(){
64 return getDefaultGeneric();
65 }
66
67 /**
68 * Initializes the generic (not EMF specific) search backend with the default settings
69 * @since 1.7
70 */
71 public static LocalSearchHints getDefaultGeneric(){
72 LocalSearchHints result = new LocalSearchHints();
73 result.useBase = true; // Should be unused; but a false value might cause surprises as an engine-default hint
74 result.rowCount = 4;
75 result.costFunction = new IndexerBasedConstraintCostFunction(StatisticsBasedConstraintCostFunction.INVERSE_NAVIGATION_PENALTY_GENERIC);
76 result.flattenCallPredicate = FLATTEN_CALL_PREDICATE.getDefaultValue();
77 result.callDelegationStrategy = ICallDelegationStrategy.FULL_BACKEND_ADHESION;
78 result.adornmentProvider = new LazyPlanningAdornments();
79 result.backendFactory = LocalSearchGenericBackendFactory.INSTANCE;
80 return result;
81 }
82
83 /**
84 * Initializes the default search backend with hybrid-enabled settings
85 * @since 2.1
86 */
87 public static LocalSearchHints getDefaultHybrid(){
88 LocalSearchHints result = getDefault();
89 result.callDelegationStrategy = ICallDelegationStrategy.PARTIAL_BACKEND_ADHESION;
90 result.flattenCallPredicate = new IFlattenCallPredicate.And(
91 new DontFlattenIncrementalPredicate(), new DontFlattenDisjunctive());
92 return result;
93 }
94
95 /**
96 * Initializes the generic (not EMF specific) search backend with hybrid-enabled settings
97 * @since 2.1
98 */
99 public static LocalSearchHints getDefaultGenericHybrid(){
100 LocalSearchHints result = getDefaultGeneric();
101 result.callDelegationStrategy = ICallDelegationStrategy.PARTIAL_BACKEND_ADHESION;
102 result.flattenCallPredicate = new IFlattenCallPredicate.And(
103 new DontFlattenIncrementalPredicate(), new DontFlattenDisjunctive());
104 return result;
105 }
106
107 public static LocalSearchHints parse(QueryEvaluationHint hint){
108 LocalSearchHints result = new LocalSearchHints();
109
110 result.useBase = USE_BASE_INDEX.getValueOrNull(hint);
111 result.rowCount = PLANNER_TABLE_ROW_COUNT.getValueOrNull(hint);
112 result.flattenCallPredicate = FLATTEN_CALL_PREDICATE.getValueOrNull(hint);
113 result.callDelegationStrategy = CALL_DELEGATION_STRATEGY.getValueOrNull(hint);
114 result.costFunction = PLANNER_COST_FUNCTION.getValueOrNull(hint);
115 result.adornmentProvider = ADORNMENT_PROVIDER.getValueOrNull(hint);
116 result.traceCollector = normalizationTraceCollector.getValueOrDefault(hint);
117
118 return result;
119 }
120
121
122 private Map<QueryHintOption<?>, Object> calculateHintMap() {
123 Map<QueryHintOption<?>, Object> map = new HashMap<>();
124 if (useBase != null){
125 USE_BASE_INDEX.insertOverridingValue(map, useBase);
126 }
127 if (rowCount != null){
128 PLANNER_TABLE_ROW_COUNT.insertOverridingValue(map, rowCount);
129 }
130 if (costFunction != null){
131 PLANNER_COST_FUNCTION.insertOverridingValue(map, costFunction);
132 }
133 if (flattenCallPredicate != null){
134 FLATTEN_CALL_PREDICATE.insertOverridingValue(map, flattenCallPredicate);
135 }
136 if (callDelegationStrategy != null){
137 CALL_DELEGATION_STRATEGY.insertOverridingValue(map, callDelegationStrategy);
138 }
139 if (adornmentProvider != null){
140 ADORNMENT_PROVIDER.insertOverridingValue(map, adornmentProvider);
141 }
142 if (traceCollector != null){
143 normalizationTraceCollector.insertOverridingValue(map, traceCollector);
144 }
145 return map;
146 }
147
148 public QueryEvaluationHint build(){
149 Map<QueryHintOption<?>, Object> map = calculateHintMap();
150 return new QueryEvaluationHint(map, backendFactory);
151 }
152
153 /**
154 * @since 1.7
155 */
156 public QueryEvaluationHint build(QueryEvaluationHint overridingHint) {
157 if (overridingHint == null)
158 return build();
159
160 IQueryBackendFactory factory = (overridingHint.getQueryBackendFactory() == null)
161 ? this.backendFactory
162 : overridingHint.getQueryBackendFactory();
163
164 Map<QueryHintOption<?>, Object> hints = calculateHintMap();
165 if (overridingHint.getBackendHintSettings() != null) {
166 hints.putAll(overridingHint.getBackendHintSettings());
167 }
168
169 return new QueryEvaluationHint(hints, factory);
170 }
171
172 public boolean isUseBase() {
173 return useBase;
174 }
175
176 public ICostFunction getCostFunction() {
177 return costFunction;
178 }
179
180 public IFlattenCallPredicate getFlattenCallPredicate() {
181 return flattenCallPredicate;
182 }
183
184 /**
185 * @since 2.1
186 */
187 public ICallDelegationStrategy getCallDelegationStrategy() {
188 return callDelegationStrategy;
189 }
190
191 public Integer getRowCount() {
192 return rowCount;
193 }
194
195 /**
196 * @since 1.5
197 */
198 public IAdornmentProvider getAdornmentProvider() {
199 return adornmentProvider;
200 }
201
202 /**
203 * @since 1.6
204 */
205 public IRewriterTraceCollector getTraceCollector() {
206 return traceCollector == null ? normalizationTraceCollector.getDefaultValue() : traceCollector;
207 }
208
209 public LocalSearchHints setUseBase(boolean useBase) {
210 this.useBase = useBase;
211 return this;
212 }
213
214 public LocalSearchHints setRowCount(int rowCount) {
215 this.rowCount = rowCount;
216 return this;
217 }
218
219 public LocalSearchHints setCostFunction(ICostFunction costFunction) {
220 this.costFunction = costFunction;
221 return this;
222 }
223
224 public LocalSearchHints setFlattenCallPredicate(IFlattenCallPredicate flattenCallPredicate) {
225 this.flattenCallPredicate = flattenCallPredicate;
226 return this;
227 }
228
229
230 /**
231 * @since 2.1
232 */
233 public LocalSearchHints setCallDelegationStrategy(ICallDelegationStrategy callDelegationStrategy) {
234 this.callDelegationStrategy = callDelegationStrategy;
235 return this;
236 }
237
238 /**
239 * @since 1.6
240 */
241 public LocalSearchHints setTraceCollector(IRewriterTraceCollector traceCollector) {
242 this.traceCollector = traceCollector;
243 return this;
244 }
245
246 /**
247 * @since 1.5
248 */
249 public LocalSearchHints setAdornmentProvider(IAdornmentProvider adornmentProvider) {
250 this.adornmentProvider = adornmentProvider;
251 return this;
252 }
253
254 public static LocalSearchHints customizeUseBase(boolean useBase){
255 return new LocalSearchHints().setUseBase(useBase);
256 }
257
258 public static LocalSearchHints customizeRowCount(int rowCount){
259 return new LocalSearchHints().setRowCount(rowCount);
260 }
261
262 public static LocalSearchHints customizeCostFunction(ICostFunction costFunction){
263 return new LocalSearchHints().setCostFunction(costFunction);
264 }
265
266 public static LocalSearchHints customizeFlattenCallPredicate(IFlattenCallPredicate predicate){
267 return new LocalSearchHints().setFlattenCallPredicate(predicate);
268 }
269
270 /**
271 * @since 2.1
272 */
273 public static LocalSearchHints customizeCallDelegationStrategy(ICallDelegationStrategy strategy){
274 return new LocalSearchHints().setCallDelegationStrategy(strategy);
275 }
276
277 /**
278 * @since 1.5
279 */
280 public static LocalSearchHints customizeAdornmentProvider(IAdornmentProvider adornmentProvider){
281 return new LocalSearchHints().setAdornmentProvider(adornmentProvider);
282 }
283
284 /**
285 * @since 1.6
286 */
287 public static LocalSearchHints customizeTraceCollector(IRewriterTraceCollector traceCollector){
288 return new LocalSearchHints().setTraceCollector(traceCollector);
289 }
290
291 @Override
292 public boolean canBeSubstitute(IMatcherCapability capability) {
293 if (capability instanceof LocalSearchHints){
294 LocalSearchHints other = (LocalSearchHints)capability;
295 /*
296 * We allow substitution of matchers if their functionally relevant settings are equal.
297 */
298 return Objects.equals(other.useBase, useBase);
299 }
300 /*
301 * For any other cases (e.g. for Rete), we cannot assume
302 * that matchers created by LS are functionally equivalent.
303 */
304 return false;
305 }
306}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java
new file mode 100644
index 00000000..295ac110
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations;
10
11import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
12import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
13import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
14
15/**
16 * Abstract base class for search operations that check only the already set variables.
17 *
18 * @noextend This class is not intended to be subclassed by clients.
19 * @since 2.0
20 */
21public abstract class CheckOperationExecutor implements ISearchOperationExecutor {
22
23 /**
24 * The executed field ensures that the second call of the check always returns false, resulting in a quick
25 * backtracking.
26 */
27 private boolean executed;
28
29 @Override
30 public void onInitialize(MatchingFrame frame, ISearchContext context) {
31 executed = false;
32 }
33
34 @Override
35 public void onBacktrack(MatchingFrame frame, ISearchContext context) {
36 }
37
38 @Override
39 public boolean execute(MatchingFrame frame, ISearchContext context) {
40 executed = executed ? false : check(frame, context);
41 return executed;
42 }
43
44 /**
45 * Executes the checking operation
46 * @since 1.7
47 */
48 protected abstract boolean check(MatchingFrame frame, ISearchContext context) ;
49
50}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java
index 37177cbf..a72c30dd 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java
@@ -4,22 +4,24 @@
4 * This program and the accompanying materials are made available under the 4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at 5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html. 6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
7 * SPDX-License-Identifier: EPL-2.0 8 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/ 9 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch; 10package tools.refinery.viatra.runtime.localsearch.operations;
10 11
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; 12import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; 13import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; 14import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
14 15
15import java.util.Iterator; 16import java.util.Iterator;
16 17
17/** 18/**
18 * An operation that can be used to enumerate all possible values for a single position based on a constraint 19 * An operation that can be used to enumerate all possible values for a single position based on a constraint
19 * @author Zoltan Ujhelyi, Akos Horvath 20 * @author Zoltan Ujhelyi, Akos Horvath
21 * @noextend This class is not intended to be subclassed by clients.
20 * @since 2.0 22 * @since 2.0
21 */ 23 */
22abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor { 24public abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
23 25
24 private Iterator<? extends T> it; 26 private Iterator<? extends T> it;
25 27
@@ -27,7 +29,6 @@ abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
27 * Returns an iterator with the possible options from the current state 29 * Returns an iterator with the possible options from the current state
28 * @since 2.0 30 * @since 2.0
29 */ 31 */
30 @SuppressWarnings("squid:S1452")
31 protected abstract Iterator<? extends T> getIterator(MatchingFrame frame, ISearchContext context); 32 protected abstract Iterator<? extends T> getIterator(MatchingFrame frame, ISearchContext context);
32 /** 33 /**
33 * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}. 34 * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}.
@@ -57,14 +58,13 @@ abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
57 } 58 }
58 59
59 /** 60 /**
60 * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute} 61 * Fixed version that handles failed unification of variables correctly.
61 * that handles failed unification of variables correctly.
62 * @param frame The matching frame to extend. 62 * @param frame The matching frame to extend.
63 * @param context The search context. 63 * @param context The search context.
64 * @return {@code true} if an extension was found, {@code false} otherwise. 64 * @return {@code true} if an extension was found, {@code false} otherwise.
65 */ 65 */
66 @Override 66 @Override
67 public boolean execute(MatchingFrame frame, ISearchContext context) { 67 public boolean execute(MatchingFrame frame, ISearchContext context) {
68 while (it.hasNext()) { 68 while (it.hasNext()) {
69 var newValue = it.next(); 69 var newValue = it.next();
70 if (fillInValue(newValue, frame, context)) { 70 if (fillInValue(newValue, frame, context)) {
@@ -72,5 +72,6 @@ abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
72 } 72 }
73 } 73 }
74 return false; 74 return false;
75 } 75 }
76
76} 77}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java
new file mode 100644
index 00000000..6fee0097
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java
@@ -0,0 +1,27 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations;
10
11import tools.refinery.viatra.runtime.matchers.context.IInputKey;
12
13/**
14 * Denotes a {@link ISearchOperation} which involves iterating over an instances of an {@link IInputKey}
15 *
16 * @author Grill Balázs
17 * @since 1.4
18 *
19 */
20public interface IIteratingSearchOperation extends ISearchOperation{
21
22 /**
23 * Get the {@link IInputKey} which instances this operation iterates upon.
24 */
25 public IInputKey getIteratedInputKey();
26
27}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java
new file mode 100644
index 00000000..d8106329
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java
@@ -0,0 +1,26 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations;
10
11import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
12
13/**
14 * Marker interface for pattern matcher call operations, such as positive and negative pattern calls or match aggregators.
15 *
16 * @author Zoltan Ujhelyi
17 * @since 1.7
18 */
19public interface IPatternMatcherOperation {
20
21 /**
22 * Returns the precomputed call information associated with the current operation
23 * @since 2.0
24 */
25 CallInformation getCallInformation();
26}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java
new file mode 100644
index 00000000..eb6243ed
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java
@@ -0,0 +1,88 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations;
10
11import java.util.List;
12import java.util.function.Function;
13
14import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
15import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
16import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
17
18/**
19 * Represents a search operation executable by the LS engine. It is expected that an operation can be shared among
20 * multiple LS matchers, but the created executors are not.
21 *
22 * @author Zoltan Ujhelyi
23 *
24 */
25public interface ISearchOperation {
26
27 /**
28 * Initializes a new operation executor for the given operation. Repeated calls must return different executor
29 * instances.
30 *
31 * @since 2.0
32 */
33 public ISearchOperationExecutor createExecutor();
34
35 /**
36 *
37 * @since 2.0
38 *
39 */
40 public interface ISearchOperationExecutor {
41
42 /**
43 * Returns the stateless operation this executor was initialized from
44 */
45 ISearchOperation getOperation();
46
47 /**
48 * During the execution of the corresponding plan, the onInitialize callback is evaluated before the execution of
49 * the operation may begin. Operations may use this method to initialize its internal data structures.
50 *
51 * @throws ViatraQueryRuntimeException
52 */
53 void onInitialize(MatchingFrame frame, ISearchContext context);
54
55 /**
56 * After the execution of the operation failed and {@link #execute(MatchingFrame, ISearchContext)} returns false, the onBacktrack
57 * callback is evaluated. Operations may use this method to clean up any temporary structures, and make the
58 * operation ready for a new execution.
59 *
60 * @throws ViatraQueryRuntimeException
61 */
62 void onBacktrack(MatchingFrame frame, ISearchContext context);
63
64 /**
65 *
66 * @param frame
67 * @param context
68 * @return true if successful, or false if backtracking needed
69 * @throws ViatraQueryRuntimeException
70 */
71 boolean execute(MatchingFrame frame, ISearchContext context);
72 }
73 /**
74 *
75 * @return the ordered list of the variable numbers that are affected by the search operation
76 */
77 List<Integer> getVariablePositions();
78
79 /**
80 * Creates a string representation of the search operation by replacing the variable numbers according to the
81 * parameter function. It is expected that the provided function does return a non-null value for each variable
82 * index that is returned by {@link #getVariablePositions()}; otherwise a {@link NullPointerException} will be
83 * thrown during the calculation of the string.
84 *
85 * @since 2.0
86 */
87 String toString(Function<Integer, String> variableMapping);
88}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java
new file mode 100644
index 00000000..38f62129
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
14import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
15import tools.refinery.viatra.runtime.matchers.util.Preconditions;
16
17/**
18 *
19 *
20 * @author Zoltan Ujhelyi
21 *
22 */
23public class MatchingFrameValueProvider implements IValueProvider {
24
25 final Map<String, Integer> nameMap;
26 final MatchingFrame frame;
27
28 public MatchingFrameValueProvider(MatchingFrame frame, Map<String, Integer> nameMap) {
29 super();
30 this.frame = frame;
31 this.nameMap = nameMap;
32 }
33
34 @Override
35 public Object getValue(String variableName) {
36 Integer index = nameMap.get(variableName);
37 Preconditions.checkArgument(index != null, "Unknown parameter variable name");
38 return frame.get(index);
39 }
40
41}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java
new file mode 100644
index 00000000..4d631635
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.Collections;
12import java.util.List;
13import java.util.Objects;
14import java.util.function.Function;
15import java.util.stream.Stream;
16
17import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
18import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
19import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
20import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
23import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
24import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
25import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
26import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
27
28/**
29 * Calculates the aggregated value of a column based on the given {@link AggregatorConstraint}
30 *
31 * @author Balázs Grill
32 * @since 1.4
33 * @noextend This class is not intended to be subclassed by clients.
34 */
35public class AggregatorCheck implements ISearchOperation, IPatternMatcherOperation {
36
37 private class Executor extends CheckOperationExecutor {
38
39 private final VolatileModifiableMaskedTuple maskedTuple;
40 private IQueryResultProvider matcher;
41
42 public Executor() {
43 super();
44 this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
45 }
46
47 @Override
48 public void onInitialize(MatchingFrame frame, ISearchContext context) {
49 super.onInitialize(frame, context);
50 maskedTuple.updateTuple(frame);
51 matcher = context.getMatcher(information.getCallWithAdornment());
52 }
53
54 @Override
55 protected boolean check(MatchingFrame frame, ISearchContext context) {
56 IMultisetAggregationOperator<?, ?, ?> operator = aggregator.getAggregator().getOperator();
57 Object result = aggregate(operator, aggregator.getAggregatedColumn(), frame);
58 return result == null ? false : Objects.equals(frame.getValue(position), result);
59 }
60
61 @SuppressWarnings("unchecked")
62 private <Domain, Accumulator, AggregateResult> AggregateResult aggregate(
63 IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator, int aggregatedColumn,
64 MatchingFrame initialFrame) {
65 maskedTuple.updateTuple(initialFrame);
66 final Stream<Domain> valueStream = matcher.getAllMatches(information.getParameterMask(), maskedTuple)
67 .map(match -> (Domain) match.get(aggregatedColumn));
68 return operator.aggregateStream(valueStream);
69 }
70
71 @Override
72 public ISearchOperation getOperation() {
73 return AggregatorCheck.this;
74 }
75 }
76
77 private final int position;
78 private final AggregatorConstraint aggregator;
79 private final CallInformation information;
80
81 /**
82 * @since 1.7
83 */
84 public AggregatorCheck(CallInformation information, AggregatorConstraint aggregator, int position) {
85 super();
86 this.information = information;
87 this.position = position;
88 this.aggregator = aggregator;
89 }
90
91 @Override
92 public ISearchOperationExecutor createExecutor() {
93 return new Executor();
94 }
95
96 @Override
97 public List<Integer> getVariablePositions() {
98 return Collections.singletonList(position);
99 }
100
101 @Override
102 public String toString() {
103 return toString(Object::toString);
104 }
105
106 @Override
107 public String toString(Function<Integer, String> variableMapping) {
108 return "check "+variableMapping.apply(position)+" = " + aggregator.getAggregator().getOperator().getName() + " find " + information.toString(variableMapping);
109 }
110
111 @Override
112 public CallInformation getCallInformation() {
113 return information;
114 }
115
116}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java
new file mode 100644
index 00000000..8f818542
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java
@@ -0,0 +1,135 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.Arrays;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Objects;
16import java.util.Queue;
17import java.util.Set;
18import java.util.function.Function;
19
20import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
21import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
22import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
23import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
24import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
25import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
26import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
27import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
28
29/**
30 * Checking for a transitive closure expressed as a local search pattern matcher. The matched pattern must have two
31 * parameters of the same model type.
32 *
33 * @author Zoltan Ujhelyi
34 * @noextend This class is not intended to be subclassed by clients.
35 * @noinstantiate This class is not intended to be instantiated by clients.
36 *
37 */
38public class BinaryTransitiveClosureCheck implements ISearchOperation, IPatternMatcherOperation {
39
40 private class Executor extends CheckOperationExecutor {
41
42 @Override
43 public void onInitialize(MatchingFrame frame, ISearchContext context) {
44 super.onInitialize(frame, context);
45 matcher = context.getMatcher(information.getCallWithAdornment());
46 // Note: second parameter is NOT bound during execution, but the first is
47 }
48
49 @Override
50 protected boolean check(MatchingFrame frame, ISearchContext context) {
51 if (checkReflexive(frame)) {
52 return true;
53 }
54
55 Object targetValue = frame.get(targetPosition);
56 Queue<Object> sourcesToEvaluate = new LinkedList<>();
57 sourcesToEvaluate.add(frame.get(sourcePosition));
58 Set<Object> sourceEvaluated = new HashSet<>();
59 final Object[] mappedFrame = new Object[] {null, null};
60 while (!sourcesToEvaluate.isEmpty()) {
61 Object currentValue = sourcesToEvaluate.poll();
62 sourceEvaluated.add(currentValue);
63 mappedFrame[0] = currentValue;
64 for (Tuple match : (Iterable<Tuple>) () -> matcher.getAllMatches(mappedFrame).iterator()) {
65 Object foundTarget = match.get(1);
66 if (targetValue.equals(foundTarget)) {
67 return true;
68 } else if (!sourceEvaluated.contains(foundTarget)) {
69 sourcesToEvaluate.add(foundTarget);
70 }
71 }
72 }
73 return false;
74 }
75
76 protected boolean checkReflexive(MatchingFrame frame) {
77 return reflexive && Objects.equals(frame.get(sourcePosition), frame.get(targetPosition));
78 }
79
80 @Override
81 public ISearchOperation getOperation() {
82 return BinaryTransitiveClosureCheck.this;
83 }
84 }
85
86 private final CallInformation information;
87 private IQueryResultProvider matcher;
88 private final int sourcePosition;
89 private final int targetPosition;
90 private final boolean reflexive;
91
92 /**
93 * The source position will be matched in the called pattern to the first parameter; while target to the second.
94 * </p>
95 * <strong>NOTE</strong>: the reflexive check call does not include the parameter type checks; appropriate type checks should be
96 * added as necessary by the operation compiler.
97 *
98 * @since 2.0
99 */
100 public BinaryTransitiveClosureCheck(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) {
101 super();
102 this.sourcePosition = sourcePosition;
103 this.targetPosition = targetPosition;
104 this.information = information;
105 this.reflexive = reflexive;
106 }
107
108 @Override
109 public ISearchOperationExecutor createExecutor() {
110 return new Executor();
111 }
112
113 @Override
114 public String toString() {
115 return toString(Object::toString);
116 }
117
118 @Override
119 public String toString(Function<Integer, String> variableMapping) {
120 String c = information.toString(variableMapping);
121 int p = c.indexOf('(');
122 String modifier = reflexive ? "*" : "+";
123 return "check find "+c.substring(0, p)+ modifier +c.substring(p);
124 }
125
126 @Override
127 public List<Integer> getVariablePositions() {
128 return Arrays.asList(sourcePosition, targetPosition);
129 }
130
131 @Override
132 public CallInformation getCallInformation() {
133 return information;
134 }
135}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java
new file mode 100644
index 00000000..4ce48af0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.Collections;
12import java.util.List;
13import java.util.function.Function;
14
15import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
16import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
17import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
18import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
19
20/**
21 * This operation handles constants in search plans by checking if a variable is bound to a certain constant value. Such
22 * operations should be executed as early as possible during plan execution.
23 *
24 * @author Marton Bur
25 * @noextend This class is not intended to be subclassed by clients.
26 */
27public class CheckConstant implements ISearchOperation {
28
29 private class Executor extends CheckOperationExecutor {
30
31 @Override
32 protected boolean check(MatchingFrame frame, ISearchContext context) {
33 return frame.get(position).equals(value);
34 }
35
36 @Override
37 public ISearchOperation getOperation() {
38 return CheckConstant.this;
39 }
40 }
41
42 private int position;
43 private Object value;
44
45 public CheckConstant(int position, Object value) {
46 this.position = position;
47 this.value = value;
48 }
49
50 @Override
51 public ISearchOperationExecutor createExecutor() {
52 return new Executor();
53 }
54
55 @Override
56 public List<Integer> getVariablePositions() {
57 return Collections.singletonList(position);
58 }
59
60 @Override
61 public String toString() {
62 return toString(Object::toString);
63 }
64
65 @Override
66 public String toString(Function<Integer, String> variableMapping) {
67 return "check constant "+variableMapping.apply(position)+"='"+value+"'";
68 }
69
70}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java
new file mode 100644
index 00000000..5f9b688a
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java
@@ -0,0 +1,96 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.List;
12import java.util.function.Function;
13
14import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
15import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
16import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
17import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
18import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
19import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
20import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
21import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
22
23/**
24 * @author Grill Balázs
25 * @since 1.4
26 * @noextend This class is not intended to be subclassed by clients.
27 */
28public class CheckPositivePatternCall implements ISearchOperation, IPatternMatcherOperation {
29
30 private class Executor extends CheckOperationExecutor {
31
32 private final VolatileModifiableMaskedTuple maskedTuple;
33 private IQueryResultProvider matcher;
34
35 public Executor() {
36 super();
37 this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
38 }
39
40 @Override
41 public void onInitialize(MatchingFrame frame, ISearchContext context) {
42 super.onInitialize(frame, context);
43 maskedTuple.updateTuple(frame);
44 matcher = context.getMatcher(information.getCallWithAdornment());
45 }
46
47 /**
48 * @since 1.5
49 */
50 protected boolean check(MatchingFrame frame, ISearchContext context) {
51 return matcher.hasMatch(information.getParameterMask(), maskedTuple);
52 }
53
54 @Override
55 public ISearchOperation getOperation() {
56 return CheckPositivePatternCall.this;
57 }
58 }
59
60 private final CallInformation information;
61
62
63 /**
64 * @since 1.7
65 */
66 public CheckPositivePatternCall(CallInformation information) {
67 super();
68 this.information = information;
69
70 }
71
72 @Override
73 public ISearchOperationExecutor createExecutor() {
74 return new Executor();
75 }
76
77 @Override
78 public List<Integer> getVariablePositions() {
79 return information.getVariablePositions();
80 }
81
82 @Override
83 public String toString() {
84 return toString(Object::toString);
85 }
86
87 @Override
88 public String toString(Function<Integer, String> variableMapping) {
89 return "check find "+information.toString(variableMapping);
90 }
91
92 @Override
93 public CallInformation getCallInformation() {
94 return information;
95 }
96}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java
new file mode 100644
index 00000000..d74c1c6f
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java
@@ -0,0 +1,92 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.Collections;
12import java.util.List;
13import java.util.function.Function;
14
15import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
16import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
17import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
18import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
21import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
22import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
23
24/**
25 * Calculates the count of matches for a called matcher
26 *
27 * @author Zoltan Ujhelyi
28 * @noextend This class is not intended to be subclassed by clients.
29 */
30public class CountCheck implements ISearchOperation, IPatternMatcherOperation {
31
32 private class Executor extends CheckOperationExecutor {
33
34 private final VolatileModifiableMaskedTuple maskedTuple;
35 private IQueryResultProvider matcher;
36
37 public Executor() {
38 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
39 }
40
41 @Override
42 public void onInitialize(MatchingFrame frame, ISearchContext context) {
43 super.onInitialize(frame, context);
44 maskedTuple.updateTuple(frame);
45 matcher = context.getMatcher(information.getCallWithAdornment());
46 }
47
48 @Override
49 protected boolean check(MatchingFrame frame, ISearchContext context) {
50 int count = matcher.countMatches(information.getParameterMask(), maskedTuple);
51 return ((Integer)frame.getValue(position)) == count;
52 }
53
54 @Override
55 public ISearchOperation getOperation() {
56 return CountCheck.this;
57 }
58 }
59
60 private final int position;
61 private final CallInformation information;
62
63 /**
64 * @since 1.7
65 */
66 public CountCheck(CallInformation information, int position) {
67 super();
68 this.information = information;
69 this.position = position;
70 }
71
72
73 @Override
74 public ISearchOperationExecutor createExecutor() {
75 return new Executor();
76 }
77
78 @Override
79 public List<Integer> getVariablePositions() {
80 return Collections.singletonList(position);
81 }
82
83 @Override
84 public String toString(Function<Integer, String> variableMapping) {
85 return "check "+variableMapping.apply(position)+" = count find "+ information.toString(variableMapping);
86 }
87
88 @Override
89 public CallInformation getCallInformation() {
90 return information;
91 }
92}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java
new file mode 100644
index 00000000..fc9efdca
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java
@@ -0,0 +1,78 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.ArrayList;
12import java.util.List;
13import java.util.Map;
14import java.util.function.Function;
15
16import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
17import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
18import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider;
21import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
22
23/**
24 * @author Zoltan Ujhelyi
25 * @noextend This class is not intended to be subclassed by clients.
26 */
27public class ExpressionCheck implements ISearchOperation {
28
29 private class Executor extends CheckOperationExecutor {
30
31 @Override
32 protected boolean check(MatchingFrame frame, ISearchContext context) {
33 try {
34 boolean result = (Boolean) evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap));
35 return result;
36 } catch (Exception e) {
37 context.getLogger().warn("Error while evaluating expression", e);
38 return false;
39 }
40 }
41
42 @Override
43 public ISearchOperation getOperation() {
44 return ExpressionCheck.this;
45 }
46 }
47
48 IExpressionEvaluator evaluator;
49 Map<String, Integer> nameMap;
50
51 public ExpressionCheck(IExpressionEvaluator evaluator, Map<String, Integer> nameMap) {
52 super();
53 this.evaluator = evaluator;
54 this.nameMap = nameMap;
55 }
56
57 @Override
58 public ISearchOperationExecutor createExecutor() {
59 return new Executor();
60 }
61
62 @Override
63 public List<Integer> getVariablePositions() {
64 // XXX not sure if this is the correct implementation to get the affected variable indicies
65 return new ArrayList<>(nameMap.values());
66 }
67
68 @Override
69 public String toString() {
70 return toString(Object::toString);
71 }
72
73 @Override
74 public String toString(Function<Integer, String> variableMapping) {
75 return "check expression "+evaluator.getShortDescription();
76 }
77
78}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java
new file mode 100644
index 00000000..5b1bca10
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java
@@ -0,0 +1,95 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.ArrayList;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15import java.util.function.Function;
16
17import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
18import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
19import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
20import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
21import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider;
22import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
23
24/**
25 * @author Grill Balázs
26 * @since 1.3
27 * @noextend This class is not intended to be subclassed by clients.
28 */
29public class ExpressionEvalCheck implements ISearchOperation {
30
31 private class Executor extends CheckOperationExecutor {
32
33 @Override
34 protected boolean check(MatchingFrame frame, ISearchContext context) {
35 try {
36 Object result = evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap));
37 if (!unwind && result != null) {
38 Object currentValue = frame.get(outputPosition);
39 return result.equals(currentValue);
40 } else if (unwind && result instanceof Set<?>) {
41 Object currentValue = frame.get(outputPosition);
42 return ((Set<?>)result).contains(currentValue);
43 }
44 } catch (Exception e) {
45 context.getLogger().warn("Error while evaluating expression", e);
46 }
47 return false;
48 }
49
50 @Override
51 public ISearchOperation getOperation() {
52 return ExpressionEvalCheck.this;
53 }
54 }
55
56 private final int outputPosition;
57 private final IExpressionEvaluator evaluator;
58 private final Map<String, Integer> nameMap;
59 private final boolean unwind;
60
61 public ExpressionEvalCheck(IExpressionEvaluator evaluator, Map<String, Integer> nameMap, int position) {
62 this(evaluator, nameMap, false, position);
63 }
64
65 /**
66 * @since 2.7
67 */
68 public ExpressionEvalCheck(IExpressionEvaluator evaluator, Map<String, Integer> nameMap, boolean unwind, int position) {
69 this.evaluator = evaluator;
70 this.nameMap = nameMap;
71 this.unwind = unwind;
72 this.outputPosition = position;
73 }
74
75 @Override
76 public ISearchOperationExecutor createExecutor() {
77 return new Executor();
78 }
79
80 @Override
81 public List<Integer> getVariablePositions() {
82 // XXX not sure if this is the correct implementation to get the affected variable indicies
83 return new ArrayList<>(nameMap.values());
84 }
85
86 @Override
87 public String toString() {
88 return toString(Object::toString);
89 }
90
91 @Override
92 public String toString(Function<Integer, String> variableMapping) {
93 return "check "+variableMapping.apply(outputPosition)+" = expression "+evaluator.getShortDescription();
94 }
95}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java
new file mode 100644
index 00000000..3f30c3c4
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java
@@ -0,0 +1,77 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.Arrays;
12import java.util.List;
13import java.util.function.Function;
14
15import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
16import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException;
17import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
18import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20
21/**
22 * @author Zoltan Ujhelyi
23 * @noextend This class is not intended to be subclassed by clients.
24 */
25public class InequalityCheck implements ISearchOperation {
26
27 private class Executor extends CheckOperationExecutor {
28
29 @Override
30 protected boolean check(MatchingFrame frame, ISearchContext context) {
31 Object source = frame.getValue(sourceLocation);
32 Object target = frame.getValue(targetLocation);
33 if (source == null) {
34 throw new LocalSearchException("Source not bound.");
35 }
36 if (target == null) {
37 throw new LocalSearchException("Target not bound");
38 }
39 return !source.equals(target);
40 }
41
42 @Override
43 public ISearchOperation getOperation() {
44 return InequalityCheck.this;
45 }
46 }
47
48 int sourceLocation;
49 int targetLocation;
50
51 public InequalityCheck(int sourceLocation, int targetLocation) {
52 super();
53 this.sourceLocation = sourceLocation;
54 this.targetLocation = targetLocation;
55 }
56
57 @Override
58 public ISearchOperationExecutor createExecutor() {
59 return new Executor();
60 }
61
62 @Override
63 public String toString() {
64 return toString(Object::toString);
65 }
66
67 @Override
68 public String toString(Function<Integer, String> variableMapping) {
69 return "check "+variableMapping.apply(sourceLocation)+" != "+variableMapping.apply(targetLocation);
70 }
71
72 @Override
73 public List<Integer> getVariablePositions() {
74 return Arrays.asList(sourceLocation, targetLocation);
75 }
76
77}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java
new file mode 100644
index 00000000..3759e1d1
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.check;
10
11import java.util.List;
12import java.util.function.Function;
13
14import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
15import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
16import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
17import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
18import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
19import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
20import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
21import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
22
23/**
24 * @author Zoltan Ujhelyi
25 * @noextend This class is not intended to be subclassed by clients.
26 */
27public class NACOperation implements ISearchOperation, IPatternMatcherOperation {
28
29 private class Executor extends CheckOperationExecutor {
30 private final VolatileModifiableMaskedTuple maskedTuple;
31 private IQueryResultProvider matcher;
32
33 private Executor() {
34 this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
35 }
36
37 @Override
38 public void onInitialize(MatchingFrame frame, ISearchContext context) {
39 super.onInitialize(frame, context);
40 maskedTuple.updateTuple(frame);
41 matcher = context.getMatcher(information.getCallWithAdornment());
42 }
43
44 @Override
45 protected boolean check(MatchingFrame frame, ISearchContext context) {
46 return !matcher.hasMatch(information.getParameterMask(), maskedTuple);
47 }
48
49 @Override
50 public ISearchOperation getOperation() {
51 return NACOperation.this;
52 }
53 }
54
55 private final CallInformation information;
56
57 /**
58 * @since 1.7
59 */
60 public NACOperation(CallInformation information) {
61 super();
62 this.information = information;
63 }
64
65 @Override
66 public ISearchOperationExecutor createExecutor() {
67 return new Executor();
68 }
69
70 @Override
71 public String toString() {
72 return toString(Object::toString);
73 }
74
75 @Override
76 public String toString(Function<Integer, String> variableMapping) {
77 return "check neg find "+information.toString(variableMapping);
78 }
79
80 @Override
81 public List<Integer> getVariablePositions() {
82 return information.getVariablePositions();
83 }
84
85 @Override
86 public CallInformation getCallInformation() {
87 return information;
88 }
89}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java
new file mode 100644
index 00000000..c2e75b7a
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import java.util.Arrays;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.List;
15import java.util.function.Function;
16import java.util.stream.Stream;
17
18import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
19import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
20import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
23import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
24import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
25import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
26import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
27
28/**
29 * Calculates the aggregated value of a column based on the given {@link AggregatorConstraint}
30 *
31 * @author Balázs Grill
32 * @since 1.4
33 */
34public class AggregatorExtend implements ISearchOperation, IPatternMatcherOperation{
35
36 private class Executor extends SingleValueExtendOperationExecutor<Object> {
37
38 private final VolatileModifiableMaskedTuple maskedTuple;
39 private IQueryResultProvider matcher;
40
41 public Executor(int position) {
42 super(position);
43 this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
44 }
45
46 @Override
47 public Iterator<?> getIterator(MatchingFrame frame, ISearchContext context) {
48 maskedTuple.updateTuple(frame);
49 matcher = context.getMatcher(information.getCallWithAdornment());
50 Object aggregate = aggregate(aggregator.getAggregator().getOperator(), aggregator.getAggregatedColumn());
51 return aggregate == null ? Collections.emptyIterator() : Collections.singletonList(aggregate).iterator();
52
53 }
54
55 @SuppressWarnings("unchecked")
56 private <Domain, Accumulator, AggregateResult> AggregateResult aggregate(
57 IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator, int aggregatedColumn) {
58 final Stream<Domain> valueStream = matcher.getAllMatches(information.getParameterMask(), maskedTuple)
59 .map(match -> (Domain) match.get(aggregatedColumn));
60 return operator.aggregateStream(valueStream);
61 }
62
63 @Override
64 public ISearchOperation getOperation() {
65 return AggregatorExtend.this;
66 }
67 }
68
69 private final AggregatorConstraint aggregator;
70 private final CallInformation information;
71 private final int position;
72
73 /**
74 * @since 1.7
75 */
76 public AggregatorExtend(CallInformation information, AggregatorConstraint aggregator, int position) {
77 this.aggregator = aggregator;
78 this.information = information;
79 this.position = position;
80 }
81
82 @Override
83 public ISearchOperationExecutor createExecutor() {
84 return new Executor(position);
85 }
86
87 @Override
88 public List<Integer> getVariablePositions() {
89 return Arrays.asList(position);
90 }
91
92 @Override
93 public String toString() {
94 return toString(Object::toString);
95 }
96
97 @Override
98 public String toString(Function<Integer, String> variableMapping) {
99 return "extend -"+variableMapping.apply(position)+" = " + aggregator.getAggregator().getOperator().getName()+" find " + information.toString(variableMapping);
100 }
101
102 @Override
103 public CallInformation getCallInformation() {
104 return information;
105 }
106}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java
new file mode 100644
index 00000000..08ecc8d6
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import java.util.Collections;
12import java.util.Iterator;
13import java.util.List;
14import java.util.function.Function;
15
16import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
17import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
18import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
21import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
22import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
23
24/**
25 * Calculates the count of matches for a called matcher
26 *
27 * @author Zoltan Ujhelyi
28 */
29public class CountOperation implements ISearchOperation, IPatternMatcherOperation{
30
31 private class Executor extends SingleValueExtendOperationExecutor<Integer> {
32 private final VolatileModifiableMaskedTuple maskedTuple;
33 private IQueryResultProvider matcher;
34
35 public Executor(int position) {
36 super(position);
37 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
38 }
39
40 @Override
41 public Iterator<Integer> getIterator(MatchingFrame frame, ISearchContext context) {
42 matcher = context.getMatcher(information.getCallWithAdornment());
43 maskedTuple.updateTuple(frame);
44 return Collections.singletonList(matcher.countMatches(information.getParameterMask(), maskedTuple)).iterator();
45 }
46
47 @Override
48 public ISearchOperation getOperation() {
49 return CountOperation.this;
50 }
51 }
52
53 private final CallInformation information;
54 private final int position;
55
56 /**
57 * @since 1.7
58 */
59 public CountOperation(CallInformation information, int position) {
60 this.information = information;
61 this.position = position;
62 }
63
64 @Override
65 public ISearchOperationExecutor createExecutor() {
66 return new Executor(position);
67 }
68
69 @Override
70 public List<Integer> getVariablePositions() {
71 return information.getVariablePositions();
72 }
73
74 @Override
75 public String toString() {
76 return toString(Object::toString);
77 }
78
79 @Override
80 public String toString(Function<Integer, String> variableMapping) {
81 return "extend -"+variableMapping.apply(position)+" = count find " + information.toString(variableMapping);
82 }
83
84 @Override
85 public CallInformation getCallInformation() {
86 return information;
87 }
88}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java
new file mode 100644
index 00000000..7186f4ac
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.function.Function;
18
19import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
20import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider;
23import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
24
25/**
26 * Calculates the result of an expression and stores it inside a variable for future reference.
27 *
28 * @author Zoltan Ujhelyi
29 *
30 */
31public class ExpressionEval implements ISearchOperation {
32
33 private class Executor extends SingleValueExtendOperationExecutor<Object> {
34
35 public Executor(int position) {
36 super(position);
37 }
38
39 @Override
40 public Iterator<?> getIterator(MatchingFrame frame, ISearchContext context) {
41 try {
42 Object result = evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap));
43 if (!unwind && result != null){
44 return Collections.singletonList(result).iterator();
45 } else if (unwind && result instanceof Set<?>) {
46 return ((Set<?>)result).iterator();
47 } else {
48 return Collections.emptyIterator();
49 }
50 } catch (Exception e) {
51 context.getLogger().warn("Error while evaluating expression", e);
52 return Collections.emptyIterator();
53 }
54 }
55
56 @Override
57 public ISearchOperation getOperation() {
58 return ExpressionEval.this;
59 }
60 }
61
62 private final IExpressionEvaluator evaluator;
63 private final boolean unwind;
64 private final Map<String, Integer> nameMap;
65 private final int position;
66
67 public ExpressionEval(IExpressionEvaluator evaluator, Map<String, Integer> nameMap, int position) {
68 this(evaluator, nameMap, false, position);
69 }
70
71 /**
72 * @since 2.7
73 */
74 public ExpressionEval(IExpressionEvaluator evaluator, Map<String, Integer> nameMap, boolean unwind, int position) {
75 this.evaluator = evaluator;
76 this.nameMap = nameMap;
77 this.unwind = unwind;
78 this.position = position;
79 }
80
81 @Override
82 public String toString() {
83 return toString(Object::toString);
84 }
85
86 @Override
87 public String toString(Function<Integer, String> variableMapping) {
88 return "extend -"+variableMapping.apply(position)+" = expression "+evaluator.getShortDescription();
89 }
90
91 @Override
92 public ISearchOperationExecutor createExecutor() {
93 return new Executor(position);
94 }
95
96 @Override
97 public List<Integer> getVariablePositions() {
98 // XXX not sure if this is the correct implementation to get the affected variable indicies
99 List<Integer> variables = new ArrayList<>();
100 variables.addAll(nameMap.values());
101 return variables;
102 }
103
104}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java
new file mode 100644
index 00000000..1250e84e
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java
@@ -0,0 +1,185 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import java.util.Arrays;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Queue;
17import java.util.Set;
18import java.util.function.Function;
19
20import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
21import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
22import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
23import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
24import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
25import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
26import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
27
28/**
29 * Checking for a transitive closure expressed as a local search pattern matcher. The matched pattern must have two
30 * parameters of the same model type.
31 *
32 * @author Zoltan Ujhelyi
33 * @since 1.7
34 *
35 */
36public abstract class ExtendBinaryTransitiveClosure implements ISearchOperation, IPatternMatcherOperation {
37
38 private class Executor extends SingleValueExtendOperationExecutor<Object> {
39
40 public Executor(int position) {
41 super(position);
42 }
43
44 @Override
45 public Iterator<?> getIterator(MatchingFrame frame, ISearchContext context) {
46 // Note: second parameter is NOT bound during execution, but the first is
47 IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment());
48
49 Queue<Object> seedsToEvaluate = new LinkedList<>();
50 final Object seedValue = frame.get(seedPosition);
51 seedsToEvaluate.add(seedValue);
52 Set<Object> seedsEvaluated = new HashSet<>();
53 Set<Object> targetsFound = new HashSet<>();
54 if (reflexive) {
55 targetsFound.add(seedValue);
56 }
57
58 while(!seedsToEvaluate.isEmpty()) {
59 Object currentValue = seedsToEvaluate.poll();
60 seedsEvaluated.add(currentValue);
61 final Object[] mappedFrame = calculateCallFrame(currentValue);
62 matcher.getAllMatches(mappedFrame).forEach(match -> {
63 Object foundTarget = getTarget(match);
64 targetsFound.add(foundTarget);
65 if (!seedsEvaluated.contains(foundTarget)) {
66 seedsToEvaluate.add(foundTarget);
67 }
68 });
69 }
70
71 return targetsFound.iterator();
72 }
73
74 @Override
75 public ISearchOperation getOperation() {
76 return ExtendBinaryTransitiveClosure.this;
77 }
78 }
79
80 /**
81 * Calculates the transitive closure of a pattern match in a forward direction (first parameter bound, second
82 * unbound).
83 * </p>
84 * <strong>Note</strong>: In case the call is reflexive, it is expected that the bound parameter already matches the universe type of the call.
85 *
86 * @since 1.7
87 */
88 public static class Forward extends ExtendBinaryTransitiveClosure {
89
90 private Object[] seedFrame = new Object[2];
91
92 /**
93 * @since 2.0
94 */
95 public Forward(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) {
96 super(information, sourcePosition, targetPosition, reflexive);
97 }
98
99 protected Object[] calculateCallFrame(Object seed) {
100 seedFrame[0] = seed;
101 seedFrame[1] = null;
102 return seedFrame;
103 }
104
105 protected Object getTarget(Tuple frame) {
106 return frame.get(1);
107 }
108 }
109
110 /**
111 * Calculates the transitive closure of a pattern match in a backward direction (first parameter unbound, second
112 * bound)
113 * </p>
114 * <strong>Note</strong>: In case the call is reflexive, it is expected that the bound parameter already matches the universe type of the call.
115 *
116 * @since 2.0
117 */
118 public static class Backward extends ExtendBinaryTransitiveClosure {
119 private Object[] seedFrame = new Object[2];
120
121 /**
122 * @since 2.0
123 */
124 public Backward(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) {
125 super(information, targetPosition, sourcePosition, reflexive);
126 }
127
128 protected Object[] calculateCallFrame(Object seed) {
129 seedFrame[0] = null;
130 seedFrame[1] = seed;
131 return seedFrame;
132 }
133
134 protected Object getTarget(Tuple frame) {
135 return frame.get(0);
136 }
137 }
138
139 private final int seedPosition;
140 private final int targetPosition;
141 private final CallInformation information;
142 private final boolean reflexive;
143
144 /**
145 * The source position will be matched in the called pattern to the first parameter; while target to the second.
146 * @since 2.0
147 */
148 protected ExtendBinaryTransitiveClosure(CallInformation information, int seedPosition, int targetPosition, boolean reflexive) {
149 this.information = information;
150 this.seedPosition = seedPosition;
151 this.targetPosition = targetPosition;
152 this.reflexive = reflexive;
153 }
154
155 protected abstract Object[] calculateCallFrame(Object seed);
156
157 protected abstract Object getTarget(Tuple frame);
158
159 @Override
160 public ISearchOperationExecutor createExecutor() {
161 return new Executor(targetPosition);
162 }
163
164 @Override
165 public String toString() {
166 return toString(Object::toString);
167 }
168
169 @Override
170 public String toString(Function<Integer, String> variableMapping) {
171 String c = information.toString(variableMapping);
172 int p = c.indexOf('(');
173 return "extend find " + c.substring(0, p) + "+" + c.substring(p);
174 }
175
176 @Override
177 public List<Integer> getVariablePositions() {
178 return Arrays.asList(seedPosition, targetPosition);
179 }
180
181 @Override
182 public CallInformation getCallInformation() {
183 return information;
184 }
185}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java
new file mode 100644
index 00000000..455236f3
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java
@@ -0,0 +1,75 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import java.util.Arrays;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.List;
15import java.util.function.Function;
16
17import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
18import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20
21/**
22 * This operation handles constants in search plans by binding a variable to a constant value. Such operations should be
23 * executed as early as possible during plan execution.
24 *
25 * @author Marton Bur
26 *
27 */
28public class ExtendConstant implements ISearchOperation {
29
30 private class Executor extends SingleValueExtendOperationExecutor<Object> {
31
32 public Executor(int position) {
33 super(position);
34 }
35
36 @Override
37 public Iterator<?> getIterator(MatchingFrame frame, ISearchContext context) {
38 return Collections.singletonList(value).iterator();
39 }
40
41 @Override
42 public ISearchOperation getOperation() {
43 return ExtendConstant.this;
44 }
45 }
46
47 private final Object value;
48 private final int position;
49
50 public ExtendConstant(int position, Object value) {
51 this.position = position;
52 this.value = value;
53 }
54
55 @Override
56 public ISearchOperationExecutor createExecutor() {
57 return new Executor(position);
58 }
59
60 @Override
61 public List<Integer> getVariablePositions() {
62 return Arrays.asList(position);
63 }
64
65 @Override
66 public String toString() {
67 return toString(Object::toString);
68 }
69
70 @Override
71 public String toString(Function<Integer, String> variableMapping) {
72 return "extend constant -"+variableMapping.apply(position)+"='"+value+"'";
73 }
74
75}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java
index 9d48c785..690a3241 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java
@@ -1,27 +1,28 @@
1/******************************************************************************* 1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the 3 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at 4 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html. 5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0 7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/ 8 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch; 9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
15import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
16import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
17import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
18import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
19import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
20 10
21import java.util.Iterator; 11import java.util.Iterator;
22import java.util.List; 12import java.util.List;
23import java.util.function.Function; 13import java.util.function.Function;
24 14
15import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
16import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
17import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor;
18import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
21import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
22import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
23import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
24import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
25
25/** 26/**
26 * @author Grill Balázs 27 * @author Grill Balázs
27 * @since 1.4 28 * @since 1.4
@@ -31,25 +32,25 @@ public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatc
31 32
32 private class Executor extends ExtendOperationExecutor<Tuple> { 33 private class Executor extends ExtendOperationExecutor<Tuple> {
33 private final VolatileModifiableMaskedTuple maskedTuple; 34 private final VolatileModifiableMaskedTuple maskedTuple;
34 35
35 public Executor() { 36 public Executor() {
36 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); 37 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
37 } 38 }
38 39
39 @Override 40 @Override
40 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) { 41 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
41 maskedTuple.updateTuple(frame); 42 maskedTuple.updateTuple(frame);
42 IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment()); 43 IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment());
43 return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator(); 44 return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator();
44 } 45 }
45 46
46 /** 47 /**
47 * @since 2.0 48 * @since 2.0
48 */ 49 */
49 @Override 50 @Override
50 protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) { 51 protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) {
51 TupleMask mask = information.getFullFrameMask(); 52 TupleMask mask = information.getFullFrameMask();
52 // The first loop clears out the elements from a possible previous iteration 53 // The first loop clears out the elements from a possible previous iteration
53 for(int i : information.getFreeParameterIndices()) { 54 for(int i : information.getFreeParameterIndices()) {
54 mask.set(frame, i, null); 55 mask.set(frame, i, null);
55 } 56 }
@@ -57,7 +58,7 @@ public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatc
57 Object oldValue = mask.getValue(frame, i); 58 Object oldValue = mask.getValue(frame, i);
58 Object valueToFill = result.get(i); 59 Object valueToFill = result.get(i);
59 if (oldValue != null && !oldValue.equals(valueToFill)){ 60 if (oldValue != null && !oldValue.equals(valueToFill)){
60 // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller. 61 // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller.
61 // In this case if the callee assigns different values the frame shall be dropped 62 // In this case if the callee assigns different values the frame shall be dropped
62 return false; 63 return false;
63 } 64 }
@@ -65,31 +66,31 @@ public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatc
65 } 66 }
66 return true; 67 return true;
67 } 68 }
68 69
69 @Override 70 @Override
70 protected void cleanup(MatchingFrame frame, ISearchContext context) { 71 protected void cleanup(MatchingFrame frame, ISearchContext context) {
71 TupleMask mask = information.getFullFrameMask(); 72 TupleMask mask = information.getFullFrameMask();
72 for(int i : information.getFreeParameterIndices()){ 73 for(int i : information.getFreeParameterIndices()){
73 mask.set(frame, i, null); 74 mask.set(frame, i, null);
74 } 75 }
75 76
76 } 77 }
77 78
78 @Override 79 @Override
79 public ISearchOperation getOperation() { 80 public ISearchOperation getOperation() {
80 return ExtendPositivePatternCall.this; 81 return ExtendPositivePatternCall.this;
81 } 82 }
82 } 83 }
83 84
84 private final CallInformation information; 85 private final CallInformation information;
85 86
86 /** 87 /**
87 * @since 1.7 88 * @since 1.7
88 */ 89 */
89 public ExtendPositivePatternCall(CallInformation information) { 90 public ExtendPositivePatternCall(CallInformation information) {
90 this.information = information; 91 this.information = information;
91 } 92 }
92 93
93 @Override 94 @Override
94 public ISearchOperationExecutor createExecutor() { 95 public ISearchOperationExecutor createExecutor() {
95 return new Executor(); 96 return new Executor();
@@ -99,17 +100,17 @@ public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatc
99 public List<Integer> getVariablePositions() { 100 public List<Integer> getVariablePositions() {
100 return information.getVariablePositions(); 101 return information.getVariablePositions();
101 } 102 }
102 103
103 @Override 104 @Override
104 public String toString() { 105 public String toString() {
105 return toString(Object::toString); 106 return toString(Object::toString);
106 } 107 }
107 108
108 @Override 109 @Override
109 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) { 110 public String toString(Function<Integer, String> variableMapping) {
110 return "extend find " + information.toString(variableMapping); 111 return "extend find " + information.toString(variableMapping);
111 } 112 }
112 113
113 @Override 114 @Override
114 public CallInformation getCallInformation() { 115 public CallInformation getCallInformation() {
115 return information; 116 return information;
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java
new file mode 100644
index 00000000..a04ffcca
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.extend;
10
11import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
12import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
13import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor;
14
15/**
16 * @since 2.0
17 * @noextend This class is not intended to be subclassed by clients.
18 */
19public abstract class SingleValueExtendOperationExecutor<T> extends ExtendOperationExecutor<T> {
20 protected int position;
21
22 /**
23 * @param position the frame position all values are to be added
24 */
25 public SingleValueExtendOperationExecutor(int position) {
26 super();
27 this.position = position;
28 }
29
30 @Override
31 protected final boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context) {
32 frame.setValue(position, newValue);
33 return true;
34 }
35
36 @Override
37 protected final void cleanup(MatchingFrame frame, ISearchContext context) {
38 frame.setValue(position, null);
39 }
40} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java
new file mode 100644
index 00000000..2b189c57
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java
@@ -0,0 +1,96 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.generic;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.List;
14import java.util.function.Function;
15import java.util.stream.Collectors;
16
17import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
18import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
19import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor;
20import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.matchers.context.IInputKey;
23import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
24import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple;
25import tools.refinery.viatra.runtime.matchers.util.Preconditions;
26
27/**
28 * @author Zoltan Ujhelyi
29 * @since 1.7
30 * @noextend This class is not intended to be subclassed by clients.
31 */
32public class GenericTypeCheck implements ISearchOperation, IIteratingSearchOperation {
33
34 private class Executor extends CheckOperationExecutor {
35 private VolatileMaskedTuple maskedTuple;
36
37 private Executor() {
38 this.maskedTuple = new VolatileMaskedTuple(callMask);
39 }
40
41 @Override
42 protected boolean check(MatchingFrame frame, ISearchContext context) {
43 maskedTuple.updateTuple(frame);
44 return context.getRuntimeContext().containsTuple(type, maskedTuple);
45 }
46
47 @Override
48 public ISearchOperation getOperation() {
49 return GenericTypeCheck.this;
50 }
51 }
52
53 private final IInputKey type;
54 private final List<Integer> positionList;
55 private final TupleMask callMask;
56
57 public GenericTypeCheck(IInputKey type, int[] positions, TupleMask callMask) {
58 this.callMask = callMask;
59 Preconditions.checkArgument(positions.length == type.getArity(),
60 "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(),
61 type.getArity(), positions.length);
62 List<Integer> modifiablePositionList = new ArrayList<>();
63 for (int position : positions) {
64 modifiablePositionList.add(position);
65 }
66 this.positionList = Collections.unmodifiableList(modifiablePositionList);
67 this.type = type;
68 }
69
70 @Override
71 public List<Integer> getVariablePositions() {
72 return positionList;
73 }
74
75 @Override
76 public ISearchOperationExecutor createExecutor() {
77 return new Executor();
78 }
79
80 @Override
81 public String toString() {
82 return toString(Object::toString);
83 }
84
85 @Override
86 public String toString(Function<Integer, String> variableMapping) {
87 return "check " + type.getPrettyPrintableName() + "("
88 + positionList.stream().map(input -> String.format("+%s", variableMapping.apply(input))).collect(Collectors.joining(", "))
89 + ")";
90 }
91
92 @Override
93 public IInputKey getIteratedInputKey() {
94 return type;
95 }
96}
diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java
index 96ac4a72..dfc3e9ad 100644
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java
@@ -1,39 +1,47 @@
1/******************************************************************************* 1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. 2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the 3 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at 4 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html. 5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0 7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/ 8 *******************************************************************************/
9package tools.refinery.store.query.viatra.internal.localsearch; 9package tools.refinery.viatra.runtime.localsearch.operations.generic;
10 10
11import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; 11import java.util.ArrayList;
12import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; 12import java.util.Collections;
13import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation; 13import java.util.Iterator;
14import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; 14import java.util.List;
15import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 15import java.util.Objects;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 16import java.util.Set;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple;
19import org.eclipse.viatra.query.runtime.matchers.util.Preconditions;
20
21import java.util.*;
22import java.util.function.Function; 17import java.util.function.Function;
23import java.util.stream.Collectors; 18import java.util.stream.Collectors;
24 19
20import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
21import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
22import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor;
23import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation;
24import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
25import tools.refinery.viatra.runtime.matchers.context.IInputKey;
26import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
27import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
28import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple;
29import tools.refinery.viatra.runtime.matchers.util.Preconditions;
30
25/** 31/**
26 * @author Zoltan Ujhelyi 32 * @author Zoltan Ujhelyi
27 * @since 1.7 33 * @since 1.7
34 * @noextend This class is not intended to be subclassed by clients.
28 */ 35 */
29public class GenericTypeExtend implements IIteratingSearchOperation { 36public class GenericTypeExtend implements IIteratingSearchOperation {
37
30 private class Executor extends ExtendOperationExecutor<Tuple> { 38 private class Executor extends ExtendOperationExecutor<Tuple> {
31 private final VolatileMaskedTuple maskedTuple; 39 private final VolatileMaskedTuple maskedTuple;
32 40
33 public Executor() { 41 public Executor() {
34 this.maskedTuple = new VolatileMaskedTuple(callMask); 42 this.maskedTuple = new VolatileMaskedTuple(callMask);
35 } 43 }
36 44
37 @Override 45 @Override
38 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) { 46 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
39 maskedTuple.updateTuple(frame); 47 maskedTuple.updateTuple(frame);
@@ -65,13 +73,13 @@ public class GenericTypeExtend implements IIteratingSearchOperation {
65 frame.setValue(position, null); 73 frame.setValue(position, null);
66 } 74 }
67 } 75 }
68 76
69 @Override 77 @Override
70 public ISearchOperation getOperation() { 78 public ISearchOperation getOperation() {
71 return GenericTypeExtend.this; 79 return GenericTypeExtend.this;
72 } 80 }
73 } 81 }
74 82
75 private final IInputKey type; 83 private final IInputKey type;
76 private final int[] positions; 84 private final int[] positions;
77 private final List<Integer> positionList; 85 private final List<Integer> positionList;
@@ -80,7 +88,7 @@ public class GenericTypeExtend implements IIteratingSearchOperation {
80 private final TupleMask callMask; 88 private final TupleMask callMask;
81 89
82 /** 90 /**
83 * 91 *
84 * @param type 92 * @param type
85 * the type to execute the extend operation on 93 * the type to execute the extend operation on
86 * @param positions 94 * @param positions
@@ -119,14 +127,14 @@ public class GenericTypeExtend implements IIteratingSearchOperation {
119 public List<Integer> getVariablePositions() { 127 public List<Integer> getVariablePositions() {
120 return positionList; 128 return positionList;
121 } 129 }
122 130
123 @Override 131 @Override
124 public String toString() { 132 public String toString() {
125 return toString(Object::toString); 133 return toString(Object::toString);
126 } 134 }
127 135
128 @Override 136 @Override
129 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) { 137 public String toString(Function<Integer, String> variableMapping) {
130 return "extend " + type.getPrettyPrintableName() + "(" 138 return "extend " + type.getPrettyPrintableName() + "("
131 + positionList.stream() 139 + positionList.stream()
132 .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input))) 140 .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input)))
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java
new file mode 100644
index 00000000..45e4fd0e
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java
@@ -0,0 +1,114 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.generic;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.List;
15import java.util.Objects;
16import java.util.function.Function;
17import java.util.stream.Collectors;
18
19import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
20import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
21import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation;
22import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
23import tools.refinery.viatra.runtime.localsearch.operations.extend.SingleValueExtendOperationExecutor;
24import tools.refinery.viatra.runtime.matchers.context.IInputKey;
25import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
26import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple;
27import tools.refinery.viatra.runtime.matchers.util.Preconditions;
28
29/**
30 * @author Zoltan Ujhelyi
31 * @since 1.7
32 * @noextend This class is not intended to be subclassed by clients.
33 */
34public class GenericTypeExtendSingleValue implements IIteratingSearchOperation {
35
36 private class Executor extends SingleValueExtendOperationExecutor<Object> {
37
38 private final VolatileMaskedTuple maskedTuple;
39
40 public Executor(int position) {
41 super(position);
42 this.maskedTuple = new VolatileMaskedTuple(callMask);
43 }
44
45 @Override
46 protected Iterator<? extends Object> getIterator(MatchingFrame frame, ISearchContext context) {
47 maskedTuple.updateTuple(frame);
48 return context.getRuntimeContext().enumerateValues(type, indexerMask, maskedTuple).iterator();
49 }
50
51 @Override
52 public ISearchOperation getOperation() {
53 return GenericTypeExtendSingleValue.this;
54 }
55 }
56
57 private final IInputKey type;
58 private final List<Integer> positionList;
59 private final TupleMask indexerMask;
60 private final TupleMask callMask;
61 private final int unboundVariableIndex;
62
63 /**
64 *
65 * @param type
66 * the type to execute the extend operation on
67 * @param positions
68 * the parameter positions that represent the variables of the input key
69 */
70 public GenericTypeExtendSingleValue(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, int unboundVariableIndex) {
71 Preconditions.checkArgument(positions.length == type.getArity(),
72 "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(),
73 type.getArity(), positions.length);
74 List<Integer> modifiablePositionList = new ArrayList<>();
75 for (int position : positions) {
76 modifiablePositionList.add(position);
77 }
78 this.unboundVariableIndex = unboundVariableIndex;
79 this.positionList = Collections.unmodifiableList(modifiablePositionList);
80 this.type = type;
81
82 this.callMask = callMask;
83 this.indexerMask = indexerMask;
84 }
85
86 @Override
87 public IInputKey getIteratedInputKey() {
88 return type;
89 }
90
91 @Override
92 public ISearchOperationExecutor createExecutor() {
93 return new Executor(unboundVariableIndex);
94 }
95
96 @Override
97 public List<Integer> getVariablePositions() {
98 return positionList;
99 }
100
101 @Override
102 public String toString() {
103 return toString(Object::toString);
104 }
105
106 @Override
107 public String toString(Function<Integer, String> variableMapping) {
108 return "extend " + type.getPrettyPrintableName() + "("
109 + positionList.stream().map(
110 input -> String.format("%s%s", Objects.equals(input, unboundVariableIndex) ? "-" : "+", variableMapping.apply(input)))
111 .collect(Collectors.joining(", "))
112 + ")";
113 }
114}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java
new file mode 100644
index 00000000..b141a7b0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java
@@ -0,0 +1,186 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.operations.util;
10
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.function.Function;
18import java.util.stream.Collectors;
19
20import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
21import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
22import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
23import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
24import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternCallBasedDeferred;
25import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure;
26import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
27import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
28import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
29import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
30import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
31import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
32
33/**
34 * This class stores a precompiled version of call-related metadata and masks for local search operations
35 *
36 * @author Zoltan Ujhelyi
37 * @since 1.7
38 */
39public final class CallInformation {
40
41 private final TupleMask fullFrameMask;
42 private final TupleMask thinFrameMask;
43 private final TupleMask parameterMask;
44 private final int[] freeParameterIndices;
45
46 private final Map<PParameter, Integer> mapping = new HashMap<>();
47 private final Set<PParameter> adornment = new HashSet<>();
48 private final PQuery referredQuery;
49 private final MatcherReference matcherReference;
50 private final IQueryReference call;
51 private CallWithAdornment callWithAdornment;
52
53 public static CallInformation create(PatternCallBasedDeferred constraint, Map<PVariable, Integer> variableMapping, Set<Integer> bindings) {
54 return new CallInformation(constraint.getActualParametersTuple(), constraint, bindings, variableMapping);
55 }
56
57 public static CallInformation create(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping, Set<Integer> bindings) {
58 return new CallInformation(pCall.getVariablesTuple(), pCall, bindings, variableMapping);
59 }
60
61 public static CallInformation create(BinaryTransitiveClosure constraint, Map<PVariable, Integer> variableMapping, Set<Integer> bindings) {
62 return new CallInformation(constraint.getVariablesTuple(), constraint, bindings, variableMapping);
63 }
64
65 /**
66 * @since 2.0
67 */
68 public static CallInformation create(BinaryReflexiveTransitiveClosure constraint, Map<PVariable, Integer> variableMapping, Set<Integer> bindings) {
69 return new CallInformation(constraint.getVariablesTuple(), constraint, bindings, variableMapping);
70 }
71
72 private CallInformation(Tuple actualParameters, IQueryReference call, final Set<Integer> bindings,
73 Map<PVariable, Integer> variableMapping) {
74 this.call = call;
75 this.referredQuery = call.getReferredQuery();
76 int keySize = actualParameters.getSize();
77 List<Integer> parameterMaskIndices = new ArrayList<>();
78 int[] fullParameterMaskIndices = new int[keySize];
79 for (int i = 0; i < keySize; i++) {
80 PParameter symbolicParameter = referredQuery.getParameters().get(i);
81 PVariable parameter = (PVariable) actualParameters.get(i);
82 Integer originalFrameIndex = variableMapping.get(parameter);
83 mapping.put(symbolicParameter, originalFrameIndex);
84 fullParameterMaskIndices[i] = originalFrameIndex;
85 if (bindings.contains(originalFrameIndex)) {
86 parameterMaskIndices.add(originalFrameIndex);
87 adornment.add(symbolicParameter);
88 }
89 }
90
91 thinFrameMask = TupleMask.fromSelectedIndices(variableMapping.size(), parameterMaskIndices);
92 fullFrameMask = TupleMask.fromSelectedIndices(variableMapping.size(), fullParameterMaskIndices);
93
94 // This second iteration is necessary as we don't know beforehand the number of bound parameters
95 int[] boundParameterIndices = new int[adornment.size()];
96 int boundIndex = 0;
97 freeParameterIndices = new int[keySize - adornment.size()];
98 int freeIndex = 0;
99 for (int i = 0; i < keySize; i++) {
100 if (bindings.contains(variableMapping.get(actualParameters.get(i)))) {
101 boundParameterIndices[boundIndex] = i;
102 boundIndex++;
103 } else {
104 freeParameterIndices[freeIndex] = i;
105 freeIndex++;
106 }
107 }
108 parameterMask = TupleMask.fromSelectedIndices(keySize, boundParameterIndices);
109 callWithAdornment = new CallWithAdornment(call, adornment);
110 matcherReference = callWithAdornment.getMatcherReference();
111 }
112
113 /**
114 * Returns a mask describing how the bound variables of a Matching Frame are mapped to parameter indexes
115 */
116 public TupleMask getThinFrameMask() {
117 return thinFrameMask;
118 }
119
120 /**
121 * Returns a mask describing how all variables of a Matching Frame are mapped to parameter indexes
122 */
123 public TupleMask getFullFrameMask() {
124 return fullFrameMask;
125 }
126
127 /**
128 * Returns a mask describing the adornment the called pattern uses
129 */
130 public TupleMask getParameterMask() {
131 return parameterMask;
132 }
133
134 public MatcherReference getReference() {
135 return matcherReference;
136 }
137
138 /**
139 * @since 2.1
140 */
141 public IQueryReference getCall() {
142 return call;
143 }
144
145 /**
146 * @since 2.1
147 */
148 public CallWithAdornment getCallWithAdornment() {
149 return callWithAdornment;
150 }
151
152 /**
153 * Returns the parameter indices that are unbound before the call
154 */
155 public int[] getFreeParameterIndices() {
156 return freeParameterIndices;
157 }
158
159 public List<Integer> getVariablePositions() {
160 List<Integer> variables = new ArrayList<>(mapping.size());
161 for(PParameter p : referredQuery.getParameters()){
162 variables.add(mapping.get(p));
163 }
164 return variables;
165 }
166
167
168
169 @Override
170 public String toString() {
171 return toString(Object::toString);
172 }
173
174 /**
175 * @since 2.0
176 */
177 public String toString(Function<Integer, String> variableMapping) {
178 return referredQuery.getFullyQualifiedName() + "("
179 + referredQuery.getParameters().stream().map(
180 input -> (adornment.contains(input) ? "+" : "-") + variableMapping.apply(mapping.get(input)))
181 .collect(Collectors.joining(","))
182 + ")";
183 }
184
185
186}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java
new file mode 100644
index 00000000..987996c9
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import java.util.Collection;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.context.IInputKey;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17
18/**
19 * Denotes an executable plan
20 *
21 * @author Grill Balázs
22 * @since 1.4
23 *
24 */
25public interface IPlanDescriptor {
26
27 /**
28 * The query which this plan implements
29 */
30 public PQuery getQuery();
31
32 /**
33 * The executable search plans for each body in the query
34 * @since 2.0
35 */
36 public Collection<SearchPlanForBody> getPlan();
37
38 /**
39 * The set of parameters this plan assumes to be bound
40 */
41 public Set<PParameter> getAdornment();
42
43 /**
44 * The collection of {@link IInputKey}s which needs to be iterated during the execution of this plan. For optimal
45 * performance, instances of these keys might be indexed.
46 */
47 public Set<IInputKey> getIteratedKeys();
48
49}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java
new file mode 100644
index 00000000..b74282ca
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
12import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints;
13import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
14import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
15import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
16import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
17
18/**
19 * @author Grill Balázs
20 * @since 1.4
21 * @noreference This interface is not intended to be referenced by clients.
22 */
23public interface IPlanProvider {
24
25 /**
26 * @throws ViatraQueryRuntimeException
27 * @since 2.1
28 */
29 public IPlanDescriptor getPlan(IQueryBackendContext backend, IOperationCompiler compiler,
30 ResultProviderRequestor resultProviderRequestor,
31 LocalSearchHints configuration, MatcherReference key);
32
33}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java
new file mode 100644
index 00000000..a5565546
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java
@@ -0,0 +1,84 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashSet;
15import java.util.List;
16import java.util.Set;
17import java.util.stream.Collectors;
18
19import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation;
20import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
21import tools.refinery.viatra.runtime.matchers.context.IInputKey;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
23import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
24
25/**
26 * @author Grill Balázs
27 * @since 1.4
28 *
29 */
30public class PlanDescriptor implements IPlanDescriptor {
31
32 private final PQuery pquery;
33 private final List<SearchPlanForBody> plan;
34 private final Set<PParameter> adornment;
35 private Set<IInputKey> iteratedKeys = null;
36
37 public PlanDescriptor(PQuery pquery, Collection<SearchPlanForBody> plan, Set<PParameter> adornment) {
38 this.pquery = pquery;
39 this.plan = new ArrayList<>(plan);
40 this.adornment = adornment;
41 }
42
43 @Override
44 public PQuery getQuery() {
45 return pquery;
46 }
47
48 @Override
49 public Collection<SearchPlanForBody> getPlan() {
50 return plan;
51 }
52
53 @Override
54 public Set<PParameter> getAdornment() {
55 return adornment;
56 }
57
58 @Override
59 public Set<IInputKey> getIteratedKeys() {
60 if (iteratedKeys == null){
61 Set<IInputKey> keys = new HashSet<>();
62 for(SearchPlanForBody bodyPlan : plan){
63 for(ISearchOperation operation : bodyPlan.getCompiledOperations()){
64 if (operation instanceof IIteratingSearchOperation){
65 keys.add(((IIteratingSearchOperation) operation).getIteratedInputKey());
66 }
67 }
68 }
69 iteratedKeys = Collections.unmodifiableSet(keys);
70 }
71 return iteratedKeys;
72 }
73
74 @Override
75 public String toString() {
76 return new StringBuilder().append("Plan for ").append(pquery.getFullyQualifiedName()).append("(")
77 .append(adornment.stream().map(PParameter::getName).collect(Collectors.joining(",")))
78 .append("{")
79 .append(plan.stream().map(Object::toString).collect(Collectors.joining("}\n{")))
80 .append("}")
81 .toString();
82 }
83
84}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java
new file mode 100644
index 00000000..3159f707
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.List;
14import java.util.Map;
15import java.util.Map.Entry;
16import java.util.stream.Collectors;
17
18import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
22
23/**
24 * A SearchPlan stores a collection of SearchPlanOperations for a fixed order of variables.
25 *
26 * @author Zoltan Ujhelyi
27 *
28 */
29public class SearchPlan {
30
31 private final List<ISearchOperation> operations;
32 private final Map<Integer, PVariable> variableMapping;
33 private final TupleMask parameterMask;
34 private final PBody body;
35
36 /**
37 * @since 2.0
38 */
39 public SearchPlan(PBody body, List<ISearchOperation> operations, TupleMask parameterMask, Map<PVariable, Integer> variableMapping) {
40 this.body = body;
41 this.operations = Collections.unmodifiableList(new ArrayList<>(operations));
42 this.parameterMask = parameterMask;
43 this.variableMapping = Collections.unmodifiableMap(variableMapping.entrySet().stream()
44 .collect(Collectors.toMap(Entry::getValue, Entry::getKey)));
45 }
46
47
48 /**
49 * Returns an immutable list of operations stored in the plan.
50 * @return the operations
51 */
52 public List<ISearchOperation> getOperations() {
53 return operations;
54 }
55
56 /**
57 * Returns an immutable map of variable mappings for the plan
58 * @since 2.0
59 */
60 public Map<Integer, PVariable> getVariableMapping() {
61 return variableMapping;
62 }
63
64 /**
65 * Returns the index of a given operation in the plan
66 * @since 2.0
67 */
68 public int getOperationIndex(ISearchOperation operation) {
69 return operations.indexOf(operation);
70 }
71
72 /**
73 * @since 2.0
74 */
75 public TupleMask getParameterMask() {
76 return parameterMask;
77 }
78
79 /**
80 * @since 2.0
81 */
82 public PBody getSourceBody() {
83 return body;
84 }
85
86 @Override
87 public String toString() {
88 StringBuilder sb = new StringBuilder();
89
90 sb.append("{\n");
91 for(ISearchOperation operation : this.getOperations()){
92 sb.append("\t");
93 sb.append(operation);
94 sb.append("\n");
95 }
96 sb.append("}");
97 return sb.toString();
98 }
99}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java
new file mode 100644
index 00000000..4a4e2450
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java
@@ -0,0 +1,210 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Akos Horvath, Gergely Varro Zoltan Ujhelyi 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10 package tools.refinery.viatra.runtime.localsearch.plan;
11
12
13import java.util.Collections;
14import java.util.List;
15import java.util.Map;
16import java.util.concurrent.CopyOnWriteArrayList;
17import java.util.stream.Collectors;
18
19import org.apache.log4j.Logger;
20import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
21import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException;
22import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdaptable;
23import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter;
24import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
25import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
26import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
27import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
28import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
29import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
30import tools.refinery.viatra.runtime.matchers.util.Preconditions;
31
32/**
33 * A search plan executor is used to execute {@link SearchPlan} instances.
34 * @noinstantiate This class is not intended to be instantiated by clients.
35 */
36public class SearchPlanExecutor implements ILocalSearchAdaptable {
37
38 private int currentOperation;
39 private final List<ISearchOperationExecutor> operations;
40 private final SearchPlan plan;
41 private final ISearchContext context;
42 private final List<ILocalSearchAdapter> adapters = new CopyOnWriteArrayList<>();
43
44 /**
45 * @since 2.0
46 */
47 public Map<Integer, PVariable> getVariableMapping() {
48 return plan.getVariableMapping();
49 }
50
51 public int getCurrentOperation() {
52 return currentOperation;
53 }
54
55 public SearchPlan getSearchPlan() {
56 return plan;
57 }
58
59 /**
60 * @since 1.7
61 */
62 public TupleMask getParameterMask() {
63 return plan.getParameterMask();
64 }
65
66 @Override
67 public void addAdapters(List<ILocalSearchAdapter> adapters) {
68 for(ILocalSearchAdapter adapter : adapters){
69 if (!this.adapters.contains(adapter)){
70 this.adapters.add(adapter);
71 adapter.adapterRegistered(this);
72 }
73 }
74 }
75
76 @Override
77 public void removeAdapters(List<ILocalSearchAdapter> adapters) {
78 for (ILocalSearchAdapter adapter : adapters) {
79 if (this.adapters.remove(adapter)){
80 adapter.adapterUnregistered(this);
81 }
82 }
83 }
84
85 /**
86 * @since 2.0
87 */
88 public SearchPlanExecutor(SearchPlan plan, ISearchContext context) {
89 Preconditions.checkArgument(context != null, "Context cannot be null");
90 this.plan = plan;
91 this.context = context;
92 operations = plan.getOperations().stream().map(ISearchOperation::createExecutor).collect(Collectors.toList());
93 this.currentOperation = -1;
94 }
95
96
97 private void init(MatchingFrame frame) {
98 if (currentOperation == -1) {
99 currentOperation++;
100 ISearchOperationExecutor operation = operations.get(currentOperation);
101 if (!adapters.isEmpty()){
102 for (ILocalSearchAdapter adapter : adapters) {
103 adapter.executorInitializing(plan, frame);
104 }
105 }
106 operation.onInitialize(frame, context);
107 } else if (currentOperation == operations.size()) {
108 currentOperation--;
109 } else {
110 throw new LocalSearchException(LocalSearchException.PLAN_EXECUTION_ERROR);
111 }
112 }
113
114
115 /**
116 * Calculates the cost of the search plan.
117 */
118 public double cost() {
119 /* default generated stub */
120 return 0.0;
121 }
122
123 /**
124 * @throws ViatraQueryRuntimeException
125 */
126 public boolean execute(MatchingFrame frame) {
127 int upperBound = operations.size() - 1;
128 init(frame);
129 operationSelected(frame, currentOperation, false);
130 while (currentOperation >= 0 && currentOperation <= upperBound) {
131 if (operations.get(currentOperation).execute(frame, context)) {
132 operationExecuted(frame, currentOperation, true);
133 currentOperation++;
134 operationSelected(frame, currentOperation, false);
135 if (currentOperation <= upperBound) {
136 ISearchOperationExecutor operation = operations.get(currentOperation);
137 operation.onInitialize(frame, context);
138 }
139 } else {
140 operationExecuted(frame, currentOperation, false);
141 ISearchOperationExecutor operation = operations.get(currentOperation);
142 operation.onBacktrack(frame, context);
143 currentOperation--;
144 operationSelected(frame, currentOperation, true);
145 }
146 }
147 boolean matchFound = currentOperation > upperBound;
148 if (matchFound && !adapters.isEmpty()) {
149 for (ILocalSearchAdapter adapter : adapters) {
150 adapter.matchFound(plan, frame);
151 }
152 }
153 return matchFound;
154 }
155
156 public void resetPlan() {
157 currentOperation = -1;
158 }
159
160 public void printDebugInformation() {
161 for (int i = 0; i < operations.size(); i++) {
162 Logger.getRootLogger().debug("[" + i + "]\t" + operations.get(i).toString());
163 }
164 }
165
166 private void operationExecuted(MatchingFrame frame, int operationIndex, boolean isSuccessful) {
167 if (!adapters.isEmpty()){
168 for (ILocalSearchAdapter adapter : adapters) {
169 adapter.operationExecuted(plan, operations.get(operationIndex).getOperation(), frame, isSuccessful);
170 }
171 }
172 }
173
174 private void operationSelected(MatchingFrame frame, int operationIndex, boolean isBacktrack) {
175 if (!adapters.isEmpty() && operationIndex >= 0 && operationIndex < operations.size()){
176 for (ILocalSearchAdapter adapter : adapters) {
177 adapter.operationSelected(plan, operations.get(operationIndex).getOperation(), frame, isBacktrack);
178 }
179 }
180 }
181
182 public ISearchContext getContext() {
183 return context;
184 }
185
186 @Override
187 public List<ILocalSearchAdapter> getAdapters() {
188 return Collections.<ILocalSearchAdapter>unmodifiableList(this.adapters);
189 }
190
191 @Override
192 public void addAdapter(ILocalSearchAdapter adapter) {
193 addAdapters(Collections.singletonList(adapter));
194 }
195
196 @Override
197 public void removeAdapter(ILocalSearchAdapter adapter) {
198 removeAdapters(Collections.singletonList(adapter));
199 }
200
201 @Override
202 public String toString() {
203 if (operations == null) {
204 return "Unspecified plan";
205 } else {
206 return operations.stream().map(Object::toString).collect(Collectors.joining("\n"));
207 }
208 }
209
210}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java
new file mode 100644
index 00000000..e0300da4
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java
@@ -0,0 +1,115 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16import java.util.stream.Collectors;
17
18import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
19import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
20import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
21import tools.refinery.viatra.runtime.matchers.psystem.PBody;
22import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
23import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
24
25/**
26 * This class is responsible for storing the results of the planner and operation compiler for a selected body.
27 * @since 2.0
28 * @noinstantiate This class is not intended to be instantiated by clients.
29 */
30public class SearchPlanForBody {
31
32 private final PBody body;
33 private final Map<PVariable, Integer> variableKeys;
34 private final int[] parameterKeys;
35 private final SubPlan plan;
36 private final List<ISearchOperation> compiledOperations;
37 private final Collection<CallWithAdornment> dependencies;
38 private final double cost;
39 private final Object internalRepresentation;
40
41 /**
42 * @since 2.1
43 */
44 public SearchPlanForBody(PBody body, Map<PVariable, Integer> variableKeys,
45 SubPlan plan, List<ISearchOperation> compiledOperations, Collection<CallWithAdornment> dependencies,
46 Object internalRepresentation, double cost) {
47 super();
48 this.body = body;
49 this.variableKeys = variableKeys;
50 this.plan = plan;
51 this.internalRepresentation = internalRepresentation;
52 this.cost = cost;
53 List<PVariable> parameters = body.getSymbolicParameterVariables();
54 parameterKeys = new int[parameters.size()];
55 for (int i=0; i<parameters.size(); i++) {
56 parameterKeys[i] = variableKeys.get(parameters.get(i));
57 }
58 this.compiledOperations = new ArrayList<>(compiledOperations.size()+1);
59 this.compiledOperations.addAll(compiledOperations);
60
61 this.dependencies = new ArrayList<>(dependencies);
62 }
63
64 public PBody getBody() {
65 return body;
66 }
67
68 public Map<PVariable, Integer> getVariableKeys() {
69 return variableKeys;
70 }
71
72 public int[] getParameterKeys() {
73 return Arrays.copyOf(parameterKeys, parameterKeys.length);
74 }
75
76 public List<ISearchOperation> getCompiledOperations() {
77 return compiledOperations;
78 }
79
80 public SubPlan getPlan() {
81 return plan;
82 }
83
84 public Collection<CallWithAdornment> getDependencies() {
85 return dependencies;
86 }
87
88 public TupleMask calculateParameterMask() {
89 return TupleMask.fromSelectedIndices(variableKeys.size(), parameterKeys);
90 }
91
92 @Override
93 public String toString() {
94 return compiledOperations.stream().map(Object::toString).collect(Collectors.joining("\n"));
95 }
96
97 /**
98 * @since 2.1
99 */
100 public double getCost() {
101 return cost;
102 }
103
104 /**
105 * @return The internal representation of the search plan, if any, for traceability
106 * @since 2.1
107 */
108 public Object getInternalRepresentation() {
109 return internalRepresentation;
110 }
111
112
113
114
115}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java
new file mode 100644
index 00000000..ed31bcb0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.plan;
10
11import java.util.Collection;
12
13import org.apache.log4j.Logger;
14import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
15import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints;
16import tools.refinery.viatra.runtime.localsearch.planner.LocalSearchPlanner;
17import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
18import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
19import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
20
21/**
22 * A plan provider implementation which caches previously calculated plans to avoid re-planning for the same adornment
23 *
24 * @author Grill Balázs
25 * @since 1.7
26 *
27 */
28public class SimplePlanProvider implements IPlanProvider {
29
30 private final Logger logger;
31
32 public SimplePlanProvider(Logger logger) {
33 this.logger = logger;
34 }
35
36 @Override
37 public IPlanDescriptor getPlan(IQueryBackendContext backend, IOperationCompiler compiler,
38 final ResultProviderRequestor resultRequestor,
39 final LocalSearchHints configuration, MatcherReference key) {
40
41 LocalSearchPlanner planner = new LocalSearchPlanner(backend, compiler, logger, configuration, resultRequestor);
42
43 Collection<SearchPlanForBody> plansForBodies = planner.plan(key.getQuery(), key.getAdornment());
44
45 IPlanDescriptor plan = new PlanDescriptor(key.getQuery(), plansForBodies, key.getAdornment());
46 return plan;
47 }
48
49}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java
new file mode 100644
index 00000000..dfd9a3c8
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java
@@ -0,0 +1,38 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.Collection;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody;
15import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18
19/**
20 * @author Zoltan Ujhelyi
21 * @since 1.7
22 */
23public interface ILocalSearchPlanner {
24
25 /**
26 * Creates executable plans for the provided query. It is required to call one of the
27 * <code>initializePlanner()</code> methods before calling this method.
28 *
29 * @param querySpec
30 * @param boundParameters
31 * a set of bound parameters
32 * @return a mapping between ISearchOperation list and a mapping, that holds a PVariable-Integer mapping for the
33 * list of ISearchOperations
34 * @throws ViatraQueryRuntimeException
35 */
36 Collection<SearchPlanForBody> plan(PQuery querySpec, Set<PParameter> boundParameters);
37
38} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java
new file mode 100644
index 00000000..72218337
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.List;
12
13import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
14
15/**
16 * @author Marton Bur
17 *
18 */
19public interface ISearchPlanCodeGenerator {
20
21 void compile(List<List<ISearchOperation>> plans);
22
23}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java
new file mode 100644
index 00000000..f44be655
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java
@@ -0,0 +1,140 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import org.apache.log4j.Logger;
20import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody;
23import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
24import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
25import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
26import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
27import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
28import tools.refinery.viatra.runtime.matchers.psystem.PBody;
29import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
30import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
31import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
32import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
33import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PBodyNormalizer;
34import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PDisjunctionRewriter;
35import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PDisjunctionRewriterCacher;
36import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PQueryFlattener;
37
38/**
39 *
40 * @author Marton Bur
41 * @noreference This class is not intended to be referenced by clients.
42 */
43public class LocalSearchPlanner implements ILocalSearchPlanner {
44
45 // Externally set tools for planning
46 private final PDisjunctionRewriter preprocessor;
47 private final LocalSearchRuntimeBasedStrategy plannerStrategy;
48 private final IQueryRuntimeContext runtimeContext;
49 private final LocalSearchHints configuration;
50 private final IOperationCompiler operationCompiler;
51 private final IQueryBackendContext context;
52 private final ResultProviderRequestor resultRequestor;
53
54 /**
55 * @param resultRequestor
56 * @since 1.7
57 */
58 public LocalSearchPlanner(IQueryBackendContext backendContext, IOperationCompiler compiler, Logger logger,
59 final LocalSearchHints configuration, ResultProviderRequestor resultRequestor)
60 {
61
62 this.runtimeContext = backendContext.getRuntimeContext();
63 this.configuration = configuration;
64 this.operationCompiler = compiler;
65 this.resultRequestor = resultRequestor;
66 PQueryFlattener flattener = new PQueryFlattener(configuration.getFlattenCallPredicate());
67 /*
68 * TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=439358: The normalizer is initialized with the false
69 * parameter to turn off unary constraint elimination to work around an issue related to plan ordering: the
70 * current implementation of the feature target checking operations expect that the source types were checked
71 * before. However, this causes duplicate constraint checks in the search plan that might affect performance
72 * negatively.
73 */
74 PBodyNormalizer normalizer = new PBodyNormalizer(runtimeContext.getMetaContext()) {
75
76 @Override
77 protected boolean shouldCalculateImpliedTypes(PQuery query) {
78 return false;
79 }
80 };
81 preprocessor = new PDisjunctionRewriterCacher(flattener, normalizer);
82
83 plannerStrategy = new LocalSearchRuntimeBasedStrategy();
84
85 context = backendContext;
86 }
87
88 /**
89 * Creates executable plans for the provided query. It is required to call one of the
90 * <code>initializePlanner()</code> methods before calling this method.
91 *
92 * @param querySpec
93 * @param boundParameters
94 * a set of bound parameters
95 * @return a mapping between ISearchOperation list and a mapping, that holds a PVariable-Integer mapping for the
96 * list of ISearchOperations
97 */
98 @Override
99 public Collection<SearchPlanForBody> plan(PQuery querySpec, Set<PParameter> boundParameters) {
100 // 1. Preparation
101 preprocessor.setTraceCollector(configuration.getTraceCollector());
102 Set<PBody> normalizedBodies = preprocessor.rewrite(querySpec.getDisjunctBodies()).getBodies();
103
104 List<SearchPlanForBody> plansForBodies = new ArrayList<>(normalizedBodies.size());
105
106 for (PBody normalizedBody : normalizedBodies) {
107 // 2. Plan creation
108 // Context has matchers for the referred Queries (IQuerySpecifications)
109 Set<PVariable> boundVariables = calculatePatternAdornmentForPlanner(boundParameters, normalizedBody);
110 PlanState searchPlanInternal = plannerStrategy.plan(normalizedBody, boundVariables, context, resultRequestor, configuration);
111 SubPlan plan = plannerStrategy.convertPlan(boundVariables, searchPlanInternal);
112 // 3. PConstraint -> POperation compilation step
113 // * Pay extra caution to extend operations, when more than one variables are unbound
114 List<ISearchOperation> compiledOperations = operationCompiler.compile(plan, boundParameters);
115 // Store the variable mappings for the plans for debug purposes (traceability information)
116 SearchPlanForBody compiledPlan = new SearchPlanForBody(normalizedBody,
117 operationCompiler.getVariableMappings(), plan, compiledOperations,
118 operationCompiler.getDependencies(),
119 searchPlanInternal, searchPlanInternal.getCost());
120
121 plansForBodies.add(compiledPlan);
122 }
123
124 return plansForBodies;
125 }
126
127 private Set<PVariable> calculatePatternAdornmentForPlanner(Set<PParameter> boundParameters, PBody normalizedBody) {
128 Map<PParameter, PVariable> parameterMapping = new HashMap<>();
129 for (ExportedParameter constraint : normalizedBody.getSymbolicParameters()) {
130 parameterMapping.put(constraint.getPatternParameter(), constraint.getParameterVariable());
131 }
132 Set<PVariable> boundVariables = new HashSet<>();
133 for (PParameter parameter : boundParameters) {
134 PVariable mappedParameter = parameterMapping.get(parameter);
135 boundVariables.add(mappedParameter);
136 }
137 return boundVariables;
138 }
139
140}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java
new file mode 100644
index 00000000..1bebe37e
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java
@@ -0,0 +1,257 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints;
20import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction;
21import tools.refinery.viatra.runtime.localsearch.planner.util.OperationCostComparator;
22import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
23import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
24import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
25import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory;
26import tools.refinery.viatra.runtime.matchers.planning.operations.PApply;
27import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
28import tools.refinery.viatra.runtime.matchers.planning.operations.PStart;
29import tools.refinery.viatra.runtime.matchers.psystem.PBody;
30import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
31import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
32import tools.refinery.viatra.runtime.matchers.util.Sets;
33
34/**
35 * This class contains the logic for local search plan calculation based on costs of the operations.
36 * Its name refers to the fact that the strategy tries to use as much information as available about
37 * the model on which the matching is initiated. When no runtime info is available, it falls back to
38 * the information available from the metamodel durint operation cost calculation.
39 *
40 * The implementation is based on the paper "Gergely Varró, Frederik Deckwerth, Martin Wieber, and Andy Schürr:
41 * An algorithm for generating model-sensitive search plans for pattern matching on EMF models"
42 * (DOI: 10.1007/s10270-013-0372-2)
43 *
44 * @author Marton Bur
45 * @noreference This class is not intended to be referenced by clients.
46 */
47public class LocalSearchRuntimeBasedStrategy {
48
49 private final OperationCostComparator infoComparator = new OperationCostComparator();
50
51
52 /**
53 * Converts a plan to the standard format
54 */
55 protected SubPlan convertPlan(Set<PVariable> initialBoundVariables, PlanState searchPlan) {
56 PBody pBody;
57 pBody = searchPlan.getAssociatedPBody();
58
59 // Create a starting plan
60 SubPlanFactory subPlanFactory = new SubPlanFactory(pBody);
61
62 // We assume that the adornment (now the bound variables) is previously set
63 SubPlan plan = subPlanFactory.createSubPlan(new PStart(initialBoundVariables));
64
65 List<PConstraintInfo> operations = searchPlan.getOperations();
66 for (PConstraintInfo pConstraintPlanInfo : operations) {
67 PConstraint pConstraint = pConstraintPlanInfo.getConstraint();
68 plan = subPlanFactory.createSubPlan(new PApply(pConstraint), plan);
69 }
70
71 return subPlanFactory.createSubPlan(new PProject(pBody.getSymbolicParameterVariables()), plan);
72 }
73
74 /**
75 * The implementation of a local search-based algorithm to create a search plan for a flattened (and normalized)
76 * PBody
77 * @param pBody for which the plan is to be created
78 * @param initialBoundVariables variables that are known to have already assigned values
79 * @param context the backend context
80 * @param resultProviderRequestor requestor for accessing result providers of called patterns
81 * @param configuration the planner configuration
82 * @return the complete search plan for the given {@link PBody}
83 * @since 2.1
84 */
85 protected PlanState plan(PBody pBody, Set<PVariable> initialBoundVariables,
86 IQueryBackendContext context, final ResultProviderRequestor resultProviderRequestor,
87 LocalSearchHints configuration) {
88 final ICostFunction costFunction = configuration.getCostFunction();
89 PConstraintInfoInferrer pConstraintInfoInferrer = new PConstraintInfoInferrer(
90 configuration.isUseBase(), context, resultProviderRequestor, costFunction::apply);
91
92 // Create mask infos
93 Set<PConstraint> constraintSet = pBody.getConstraints();
94 List<PConstraintInfo> constraintInfos =
95 pConstraintInfoInferrer.createPConstraintInfos(constraintSet);
96
97 // Calculate the characteristic function
98 // The characteristic function tells whether a given adornment is backward reachable from the (B)* state, where
99 // each variable is bound.
100 // The characteristic function is represented as a set of set of variables
101 // TODO this calculation is not not implemented yet, thus the contents of the returned set is not considered later
102 List<Set<PVariable>> reachableBoundVariableSets = reachabilityAnalysis(pBody, constraintInfos);
103 int k = configuration.getRowCount();
104 PlanState searchPlan = calculateSearchPlan(pBody, initialBoundVariables, k, reachableBoundVariableSets, constraintInfos);
105 return searchPlan;
106 }
107
108 private PlanState calculateSearchPlan(PBody pBody, Set<PVariable> initialBoundVariables, int k,
109 List<Set<PVariable>> reachableBoundVariableSets, List<PConstraintInfo> allMaskInfos) {
110
111 List<PConstraintInfo> allPotentialExtendInfos = new ArrayList<>();
112 List<PConstraintInfo> allPotentialCheckInfos = new ArrayList<>();
113 Map<PVariable, List<PConstraintInfo>> checkOpsByVariables = new HashMap<>();
114 Map<PVariable, Collection<PConstraintInfo>> extendOpsByBoundVariables = new HashMap<>();
115
116 for (PConstraintInfo op : allMaskInfos) {
117 if (op.getFreeVariables().isEmpty()) { // CHECK
118 allPotentialCheckInfos.add(op);
119 } else { // EXTEND
120 allPotentialExtendInfos.add(op);
121 for (PVariable variable : op.getBoundVariables()) {
122 extendOpsByBoundVariables.computeIfAbsent(variable, v -> new ArrayList<>()).add(op);
123 }
124 }
125 }
126 // For CHECKs only, we must start from lists that are ordered by the cost of the constraint application
127 Collections.sort(allPotentialCheckInfos, infoComparator); // costs are eagerly needed for check ops
128 for (PConstraintInfo op : allPotentialCheckInfos) {
129 for (PVariable variable : op.getBoundVariables()) {
130 checkOpsByVariables.computeIfAbsent(variable, v -> new ArrayList<>()).add(op);
131 }
132 }
133 // costs are not needed for extend ops until they are first applied (TODO make cost computaiton on demand)
134
135
136 // rename for better understanding
137 Set<PVariable> boundVariables = initialBoundVariables;
138 Set<PVariable> freeVariables = Sets.difference(pBody.getUniqueVariables(), initialBoundVariables);
139
140 int variableCount = pBody.getUniqueVariables().size();
141 int n = freeVariables.size();
142
143 List<List<PlanState>> stateTable = initializeStateTable(k, n);
144
145 // Set initial state: begin with an empty operation list
146 PlanState initialState = new PlanState(pBody, boundVariables);
147
148 // Initial state creation, categorizes all operations; add present checks to operationsList
149 initialState.updateExtends(allPotentialExtendInfos);
150 initialState.applyChecks(allPotentialCheckInfos);
151 stateTable.get(n).add(0, initialState);
152
153 // stateTable.get(0) will contain the states with adornment B*
154 for (int i = n; i > 0; i--) {
155 for (int j = 0; j < k && j < stateTable.get(i).size(); j++) {
156 PlanState currentState = stateTable.get(i).get(j);
157
158 for (PConstraintInfo constraintInfo : currentState.getPresentExtends()) {
159 // for each present EXTEND operation
160 PlanState newState = calculateNextState(currentState, constraintInfo);
161 // also eagerly perform any CHECK operations that become applicable (extends still deferred)
162 newState.applyChecksBasedOnDelta(checkOpsByVariables);
163
164 if(currentState.getBoundVariables().size() == newState.getBoundVariables().size()){
165 // This means no variable binding was done, go on with the next constraint info
166 continue;
167 }
168 int i2 = variableCount - newState.getBoundVariables().size();
169
170 List<Integer> newIndices = determineIndices(stateTable, i2, newState, k);
171 int a = newIndices.get(0);
172 int c = newIndices.get(1);
173
174 if (checkInsertCondition(stateTable.get(i2), newState, reachableBoundVariableSets, a, c, k)) {
175 updateExtends(newState, currentState, extendOpsByBoundVariables); // preprocess next steps
176 insert(stateTable,i2, newState, a, c, k);
177 }
178 }
179 }
180 }
181
182 return stateTable.get(0).get(0);
183 }
184
185 private List<List<PlanState>> initializeStateTable(int k, int n) {
186 List<List<PlanState>> stateTable = new ArrayList<>();
187 // Initialize state table and fill it with null
188 for (int i = 0; i <= n ; i++) {
189 stateTable.add(new ArrayList<>());
190 }
191 return stateTable;
192 }
193
194 private void insert(List<List<PlanState>> stateTable, int idx, PlanState newState, int a, int c, int k) {
195 stateTable.get(idx).add(c, newState);
196 while(stateTable.get(idx).size() > k){
197 // Truncate back to size k when grows too big
198 stateTable.set(idx, stateTable.get(idx).subList(0, k));
199 }
200 }
201
202 private void updateExtends(PlanState newState, PlanState currentState,
203 Map<PVariable, ? extends Collection<PConstraintInfo>> extendOpsByBoundVariables)
204 {
205 List<PConstraintInfo> presentExtends = currentState.getPresentExtends();
206
207 // Recategorize operations
208 newState.updateExtendsBasedOnDelta(presentExtends, extendOpsByBoundVariables);
209
210 return;
211 }
212
213 private boolean checkInsertCondition(List<PlanState> list, PlanState newState,
214 List<Set<PVariable>> reachableBoundVariableSets, int a, int c, int k) {
215// boolean isAmongBestK = (a == (k + 1)) && c < a && reachableBoundVariableSets.contains(newState.getBoundVariables());
216 boolean isAmongBestK = a == k && c < a ;
217 boolean isBetterThanCurrent = a < k && c <= a;
218
219 return isAmongBestK || isBetterThanCurrent;
220 }
221
222 private List<Integer> determineIndices(List<List<PlanState>> stateTable, int i2, PlanState newState, int k) {
223 int a = k;
224 int c = 0;
225 List<Integer> acIndices = new ArrayList<>();
226 for (int j = 0; j < k; j++) {
227 if (j < stateTable.get(i2).size()) {
228 PlanState stateInTable = stateTable.get(i2).get(j);
229 if (newState.getBoundVariables().equals(stateInTable.getBoundVariables())) {
230 // The new state has the same adornment as the stored one - they are not adornment disjoint
231 a = j;
232 }
233 if (newState.getCost() >= stateInTable.getCost()) {
234 c = j + 1;
235 }
236 } else {
237 break;
238 }
239 }
240
241 acIndices.add(a);
242 acIndices.add(c);
243 return acIndices;
244 }
245
246 private PlanState calculateNextState(PlanState currentState, PConstraintInfo constraintInfo) {
247 return currentState.cloneWithApplied(constraintInfo);
248 }
249
250 private List<Set<PVariable>> reachabilityAnalysis(PBody pBody, List<PConstraintInfo> constraintInfos) {
251 // TODO implement reachability analisys, also save/persist the results somewhere
252 List<Set<PVariable>> reachableBoundVariableSets = new ArrayList<>();
253 return reachableBoundVariableSets;
254 }
255
256
257}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java
new file mode 100644
index 00000000..b98dd12e
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11
12/**
13 * Expresses the state of a PConstraint application
14 * condition with respect to a given adornment.
15 *
16 * @author Marton Bur
17 * @noreference This enum is not intended to be referenced by clients.
18 */
19public enum PConstraintCategory {
20 /*
21 * During plan creation an operation is considered a past
22 * operation, if an already bound variable is free in the
23 * mask of the operation.
24 * (Mask of the operation: the required binding state of
25 * the affected variables)
26 */
27 PAST,
28 /*
29 * The binding states of the variables in the operation
30 * mask correspond to the current binding states of the
31 * variables in the search plan
32 */
33 PRESENT,
34 /*
35 * There is at least one bound variable in the mask of
36 * a future operation that is still free at the current
37 * state of the plan. Also, a future operation can't be
38 * PAST.
39 */
40 FUTURE;
41}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java
new file mode 100644
index 00000000..c2c76ef2
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java
@@ -0,0 +1,142 @@
1/**
2 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Danil 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 */
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.Collections;
12import java.util.LinkedHashSet;
13import java.util.Set;
14import java.util.function.Function;
15
16import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
17import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
18import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
19import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess;
20import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
21import tools.refinery.viatra.runtime.matchers.psystem.PBody;
22import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
23import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
24import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
25
26/**
27 * Wraps a PConstraint together with information required for the planner. Currently contains information about the expected binding state of
28 * the affected variables also called application condition, and the cost of the enforcement, based on the meta and/or the runtime context.
29 *
30 * @author Marton Bur
31 * @noreference This class is not intended to be referenced by clients.
32 */
33public class PConstraintInfo implements IConstraintEvaluationContext {
34
35 private PConstraint constraint;
36 private Set<PVariable> boundMaskVariables;
37 private Set<PVariable> freeMaskVariables;
38 private Set<PConstraintInfo> sameWithDifferentBindings;
39 private IQueryRuntimeContext runtimeContext;
40 private QueryAnalyzer queryAnalyzer;
41 private IQueryResultProviderAccess resultProviderAccess;
42 private ResultProviderRequestor resultRequestor;
43
44 private Double cost;
45 private Function<IConstraintEvaluationContext, Double> costFunction;
46
47
48 /**
49 * Instantiates the wrapper
50 * @param constraintfor which the information is added and stored
51 * @param boundMaskVariables the bound variables in the operation mask
52 * @param freeMaskVariables the free variables in the operation mask
53 * @param sameWithDifferentBindings during the planning process, multiple operation adornments are considered for a constraint, so that it
54 * is represented by multiple plan infos. This parameter contains all plan infos that are for the same
55 * constraint, but with different adornment
56 * @param context the query backend context
57 */
58 public PConstraintInfo(PConstraint constraint, Set<PVariable> boundMaskVariables, Set<PVariable> freeMaskVariables,
59 Set<PConstraintInfo> sameWithDifferentBindings,
60 IQueryBackendContext context,
61 ResultProviderRequestor resultRequestor,
62 Function<IConstraintEvaluationContext, Double> costFunction) {
63 this.constraint = constraint;
64 this.costFunction = costFunction;
65 this.boundMaskVariables = new LinkedHashSet<>(boundMaskVariables);
66 this.freeMaskVariables = new LinkedHashSet<>(freeMaskVariables);
67 this.sameWithDifferentBindings = sameWithDifferentBindings;
68 this.resultRequestor = resultRequestor;
69 this.runtimeContext = context.getRuntimeContext();
70 this.queryAnalyzer = context.getQueryAnalyzer();
71 this.resultProviderAccess = context.getResultProviderAccess();
72
73 this.cost = null; // cost will be computed lazily (esp. important for pattern calls)
74 }
75
76 @Override
77 public IQueryRuntimeContext getRuntimeContext() {
78 return runtimeContext;
79 }
80
81 @Override
82 public QueryAnalyzer getQueryAnalyzer() {
83 return queryAnalyzer;
84 }
85
86 @Override
87 public PConstraint getConstraint() {
88 return constraint;
89 }
90
91 @Override
92 public Set<PVariable> getFreeVariables() {
93 return freeMaskVariables;
94 }
95
96 @Override
97 public Set<PVariable> getBoundVariables() {
98 return boundMaskVariables;
99 }
100
101 public Set<PConstraintInfo> getSameWithDifferentBindings() {
102 return sameWithDifferentBindings;
103 }
104
105 public double getCost() {
106 if (cost == null) {
107 // Calculate cost of the constraint based on its type
108 cost = costFunction.apply(this);
109 }
110 return cost;
111 }
112
113 public PConstraintCategory getCategory(PBody pBody, Set<PVariable> boundVariables) {
114 if (!Collections.disjoint(boundVariables, this.freeMaskVariables)) {
115 return PConstraintCategory.PAST;
116 } else if (!boundVariables.containsAll(this.boundMaskVariables)) {
117 return PConstraintCategory.FUTURE;
118 } else {
119 return PConstraintCategory.PRESENT;
120 }
121 }
122
123 @Override
124 public String toString() {
125 return String.format("%s, bound variables: %s, cost: \"%.2f\"", constraint.toString(), boundMaskVariables.toString(), cost);
126 }
127
128 /**
129 * @deprecated use {@link #resultProviderRequestor()}
130 */
131 @Override
132 @Deprecated
133 public IQueryResultProviderAccess resultProviderAccess() {
134 return resultProviderAccess;
135 }
136
137 @Override
138 public ResultProviderRequestor resultProviderRequestor() {
139 return resultRequestor;
140 }
141
142}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java
new file mode 100644
index 00000000..eeac07ce
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java
@@ -0,0 +1,278 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
12import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
13import tools.refinery.viatra.runtime.matchers.context.IInputKey;
14import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
17import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*;
18import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.AbstractTransitiveClosure;
19import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
20import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
21import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
23import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
25import tools.refinery.viatra.runtime.matchers.util.Sets;
26
27import java.util.*;
28import java.util.function.Function;
29import java.util.function.Predicate;
30import java.util.stream.Collectors;
31import java.util.stream.Stream;
32
33
34/**
35 * @author Grill Balázs
36 * @noreference This class is not intended to be referenced by clients.
37 */
38class PConstraintInfoInferrer {
39
40 private static final Predicate<PVariable> SINGLE_USE_VARIABLE = input -> input != null && input.getReferringConstraints().size() == 1;
41
42 private final boolean useIndex;
43 private final Function<IConstraintEvaluationContext, Double> costFunction;
44 private final IQueryBackendContext context;
45 private final ResultProviderRequestor resultRequestor;
46
47
48 public PConstraintInfoInferrer(boolean useIndex,
49 IQueryBackendContext backendContext,
50 ResultProviderRequestor resultRequestor,
51 Function<IConstraintEvaluationContext, Double> costFunction) {
52 this.useIndex = useIndex;
53 this.context = backendContext;
54 this.resultRequestor = resultRequestor;
55 this.costFunction = costFunction;
56 }
57
58
59 /**
60 * Create all possible application condition for all constraint
61 *
62 * @param constraintSet the set of constraints
63 * @return a collection of the wrapper PConstraintInfo objects with all the allowed application conditions
64 */
65 public List<PConstraintInfo> createPConstraintInfos(Set<PConstraint> constraintSet) {
66 List<PConstraintInfo> constraintInfos = new ArrayList<>();
67
68 for (PConstraint pConstraint : constraintSet) {
69 createPConstraintInfoDispatch(constraintInfos, pConstraint);
70 }
71 return constraintInfos;
72 }
73
74 private void createPConstraintInfoDispatch(List<PConstraintInfo> resultList, PConstraint pConstraint){
75 if(pConstraint instanceof ExportedParameter){
76 createConstraintInfoExportedParameter(resultList, (ExportedParameter) pConstraint);
77 } else if(pConstraint instanceof TypeConstraint){
78 createConstraintInfoTypeConstraint(resultList, (TypeConstraint)pConstraint);
79 } else if(pConstraint instanceof TypeFilterConstraint){
80 createConstraintInfoTypeFilterConstraint(resultList, (TypeFilterConstraint)pConstraint);
81 } else if(pConstraint instanceof ConstantValue){
82 createConstraintInfoConstantValue(resultList, (ConstantValue)pConstraint);
83 } else if (pConstraint instanceof Inequality){
84 createConstraintInfoInequality(resultList, (Inequality) pConstraint);
85 } else if (pConstraint instanceof ExpressionEvaluation){
86 createConstraintInfoExpressionEvaluation(resultList, (ExpressionEvaluation)pConstraint);
87 } else if (pConstraint instanceof AggregatorConstraint){
88 createConstraintInfoAggregatorConstraint(resultList, pConstraint, ((AggregatorConstraint) pConstraint).getResultVariable());
89 } else if (pConstraint instanceof PatternMatchCounter){
90 createConstraintInfoAggregatorConstraint(resultList, pConstraint, ((PatternMatchCounter) pConstraint).getResultVariable());
91 } else if (pConstraint instanceof PositivePatternCall){
92 createConstraintInfoPositivePatternCall(resultList, (PositivePatternCall) pConstraint);
93 } else if (pConstraint instanceof AbstractTransitiveClosure) {
94 createConstraintInfoBinaryTransitiveClosure(resultList, (AbstractTransitiveClosure) pConstraint);
95 } else{
96 createConstraintInfoGeneric(resultList, pConstraint);
97 }
98 }
99
100 private void createConstraintInfoConstantValue(List<PConstraintInfo> resultList,
101 ConstantValue pConstraint) {
102 // A ConstantValue constraint has a single variable, which is allowed to be unbound
103 // (extending through ConstantValue is considered a cheap operation)
104 Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
105 Set<? extends Set<PVariable>> bindings = Sets.powerSet(affectedVariables);
106 doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
107 }
108
109
110 private void createConstraintInfoPositivePatternCall(List<PConstraintInfo> resultList,
111 PositivePatternCall pCall) {
112 // A pattern call can have any of its variables unbound
113 Set<PVariable> affectedVariables = pCall.getAffectedVariables();
114 // IN parameters cannot be unbound and
115 // OUT parameters cannot be bound
116 Tuple variables = pCall.getVariablesTuple();
117 final Set<PVariable> inVariables = new HashSet<>();
118 Set<PVariable> inoutVariables = new HashSet<>();
119 List<PParameter> parameters = pCall.getReferredQuery().getParameters();
120 for(int i=0;i<parameters.size();i++){
121 switch(parameters.get(i).getDirection()){
122 case IN:
123 inVariables.add((PVariable) variables.get(i));
124 break;
125 case INOUT:
126 inoutVariables.add((PVariable) variables.get(i));
127 break;
128 case OUT:
129 default:
130 break;
131
132 }
133 }
134 Iterable<Set<PVariable>> bindings = Sets.powerSet(inoutVariables).stream()
135 .map(input -> Stream.concat(input.stream(), inVariables.stream()).collect(Collectors.toSet()))
136 .collect(Collectors.toSet());
137
138 doCreateConstraintInfos(resultList, pCall, affectedVariables, bindings);
139 }
140
141 private void createConstraintInfoBinaryTransitiveClosure(List<PConstraintInfo> resultList,
142 AbstractTransitiveClosure closure) {
143 // A pattern call can have any of its variables unbound
144
145 List<PParameter> parameters = closure.getReferredQuery().getParameters();
146 Tuple variables = closure.getVariablesTuple();
147
148 Set<Set<PVariable>> bindings = new HashSet<>();
149 PVariable firstVariable = (PVariable) variables.get(0);
150 PVariable secondVariable = (PVariable) variables.get(1);
151 // Check is always supported
152 bindings.add(new HashSet<>(Arrays.asList(firstVariable, secondVariable)));
153 // If first parameter is not bound mandatorily, it can be left out
154 if (parameters.get(0).getDirection() != PParameterDirection.IN) {
155 bindings.add(Collections.singleton(secondVariable));
156 }
157 // If second parameter is not bound mandatorily, it can be left out
158 if (parameters.get(1).getDirection() != PParameterDirection.IN) {
159 bindings.add(Collections.singleton(firstVariable));
160 }
161
162 doCreateConstraintInfos(resultList, closure, closure.getAffectedVariables(), bindings);
163 }
164
165
166
167 private void createConstraintInfoExportedParameter(List<PConstraintInfo> resultList,
168 ExportedParameter parameter) {
169 // In case of an exported parameter constraint, the parameter must be bound in order to execute
170 Set<PVariable> affectedVariables = parameter.getAffectedVariables();
171 doCreateConstraintInfos(resultList, parameter, affectedVariables, Collections.singleton(affectedVariables));
172 }
173
174 private void createConstraintInfoExpressionEvaluation(List<PConstraintInfo> resultList,
175 ExpressionEvaluation expressionEvaluation) {
176 // An expression evaluation can only have its output variable unbound. All other variables shall be bound
177 PVariable output = expressionEvaluation.getOutputVariable();
178 Set<Set<PVariable>> bindings = new HashSet<>();
179 Set<PVariable> affectedVariables = expressionEvaluation.getAffectedVariables();
180 // All variables bound -> check
181 bindings.add(affectedVariables);
182 // Output variable is not bound -> extend
183 bindings.add(affectedVariables.stream().filter(var -> !Objects.equals(var, output)).collect(Collectors.toSet()));
184 doCreateConstraintInfos(resultList, expressionEvaluation, affectedVariables, bindings);
185 }
186
187 private void createConstraintInfoTypeFilterConstraint(List<PConstraintInfo> resultList,
188 TypeFilterConstraint filter){
189 // In case of type filter, all affected variables must be bound in order to execute
190 Set<PVariable> affectedVariables = filter.getAffectedVariables();
191 doCreateConstraintInfos(resultList, filter, affectedVariables, Collections.singleton(affectedVariables));
192 }
193
194 private void createConstraintInfoInequality(List<PConstraintInfo> resultList,
195 Inequality inequality){
196 // In case of inequality, all affected variables must be bound in order to execute
197 Set<PVariable> affectedVariables = inequality.getAffectedVariables();
198 doCreateConstraintInfos(resultList, inequality, affectedVariables, Collections.singleton(affectedVariables));
199 }
200
201 private void createConstraintInfoAggregatorConstraint(List<PConstraintInfo> resultList,
202 PConstraint pConstraint, PVariable resultVariable){
203 Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
204
205 // The only variables which can be unbound are single-use
206 Set<PVariable> canBeUnboundVariables =
207 Stream.concat(Stream.of(resultVariable), affectedVariables.stream().filter(SINGLE_USE_VARIABLE)).collect(Collectors.toSet());
208
209 Set<Set<PVariable>> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables);
210
211 doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
212 }
213
214 /**
215 *
216 * @param canBeUnboundVariables Variables which are allowed to be unbound
217 * @param affectedVariables All affected variables
218 * @return The set of possible bound variable sets
219 */
220 private Set<Set<PVariable>> calculatePossibleBindings(Set<PVariable> canBeUnboundVariables, Set<PVariable> affectedVariables){
221 final Set<PVariable> mustBindVariables = affectedVariables.stream().filter(input -> !canBeUnboundVariables.contains(input)).collect(Collectors.toSet());
222 return Sets.powerSet(canBeUnboundVariables).stream()
223 .map(input -> {
224 //some variables have to be bound before executing this constraint
225 Set<PVariable> result= new HashSet<>(input);
226 result.addAll(mustBindVariables);
227 return result;
228 })
229 .collect(Collectors.toSet());
230 }
231
232 private void createConstraintInfoGeneric(List<PConstraintInfo> resultList, PConstraint pConstraint){
233 Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
234
235 // The only variables which can be unbound are single use variables
236 Set<PVariable> canBeUnboundVariables = affectedVariables.stream().filter(SINGLE_USE_VARIABLE).collect(Collectors.toSet());
237
238 Set<Set<PVariable>> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables);
239
240 doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
241 }
242
243 private void createConstraintInfoTypeConstraint(List<PConstraintInfo> resultList,
244 TypeConstraint typeConstraint) {
245 Set<PVariable> affectedVariables = typeConstraint.getAffectedVariables();
246 Set<? extends Set<PVariable>> bindings = null;
247
248 IInputKey inputKey = typeConstraint.getSupplierKey();
249 if(inputKey.isEnumerable()){
250 bindings = Sets.powerSet(affectedVariables);
251 }else{
252 // For not enumerable types, this constraint can only be a check
253 bindings = Collections.singleton(affectedVariables);
254 }
255
256 doCreateConstraintInfos(resultList, typeConstraint, affectedVariables, bindings);
257 }
258
259 private void doCreateConstraintInfos(List<PConstraintInfo> constraintInfos,
260 PConstraint pConstraint, Set<PVariable> affectedVariables, Iterable<? extends Set<PVariable>> bindings) {
261 Set<PConstraintInfo> sameWithDifferentBindings = new HashSet<>();
262 for (Set<PVariable> boundVariables : bindings) {
263
264 PConstraintInfo info = new PConstraintInfo(pConstraint, boundVariables,
265 affectedVariables.stream().filter(input -> !boundVariables.contains(input)).collect(Collectors.toSet()),
266 sameWithDifferentBindings, context, resultRequestor, costFunction);
267 constraintInfos.add(info);
268 sameWithDifferentBindings.add(info);
269 }
270 }
271
272 private Set<Set<PVariable>> excludeUnnavigableOperationMasks(TypeConstraint typeConstraint, Set<? extends Set<PVariable>> bindings) {
273 PVariable firstVariable = typeConstraint.getVariableInTuple(0);
274 return bindings.stream().filter(
275 boundVariablesSet -> (boundVariablesSet.isEmpty() || boundVariablesSet.contains(firstVariable)))
276 .collect(Collectors.toSet());
277 }
278}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java
new file mode 100644
index 00000000..e93b07bc
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java
@@ -0,0 +1,283 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Comparator;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import tools.refinery.viatra.runtime.localsearch.planner.util.OperationCostComparator;
21import tools.refinery.viatra.runtime.matchers.algorithms.OrderedIterableMerge;
22import tools.refinery.viatra.runtime.matchers.psystem.PBody;
23import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
24import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
25
26/**
27 * This class represents the state of the plan during planning.
28 *
29 * <p> A PlanState represents a sequence of operations (operationsList) and caches the computed cost
30 * for this operation sequence. The list and the cost are initialized in the constructor.
31 * However, #categorizeChecks() also updates the operations list (by suffixing checks)
32 *
33 * @author Marton Bur
34 * @noreference This class is not intended to be referenced by clients.
35 */
36public class PlanState {
37
38 private final PBody pBody;
39 private final List<PConstraintInfo> operationsList;
40 private final Set<PVariable> boundVariables;
41 private final Collection<PVariable> deltaVariables; /* bound since ancestor plan */
42 private final Set<PConstraint> enforcedConstraints;
43
44 private double cummulativeProduct;
45 private double cost;
46
47 private static Comparator<PConstraintInfo> infoComparator = new OperationCostComparator();
48
49 /*
50 * For a short explanation of past, present and future operations,
51 * see class
52 */
53 private List<PConstraintInfo> presentExtends;
54
55 /**
56 * Creates an initial state
57 */
58 public PlanState(PBody pBody, Set<PVariable> boundVariables) {
59
60 this(pBody, new ArrayList<>(), boundVariables, boundVariables /* also the delta */,
61 0.0 /* initial cost */, 1.0 /*initial branch count */);
62 }
63
64 public PlanState cloneWithApplied(PConstraintInfo op) {
65 // Create operation list based on the current state
66 ArrayList<PConstraintInfo> newOperationsList =
67 // pre-reserve excess capacity for later addition of CHECK ops
68 new ArrayList<>(pBody.getConstraints().size());
69 newOperationsList.addAll(this.getOperations());
70 newOperationsList.add(op);
71
72 // Bind the variables of the op
73 Collection<PVariable> deltaVariables = op.getFreeVariables();
74 Set<PVariable> allBoundVariables =
75 // pre-reserve exact capacity as variables are known
76 // (will not be affected by adding CHECK ops later)
77 new HashSet<>(this.getBoundVariables().size() + deltaVariables.size());
78 allBoundVariables.addAll(this.getBoundVariables());
79 allBoundVariables.addAll(deltaVariables);
80
81 PlanState newState = new PlanState(getAssociatedPBody(), newOperationsList, allBoundVariables, deltaVariables,
82 cost, cummulativeProduct);
83 newState.accountNewOperation(op);
84 return newState;
85 }
86
87 private PlanState(PBody pBody, List<PConstraintInfo> operationsList,
88 Set<PVariable> boundVariables, Collection<PVariable> deltaVariables,
89 double cost, double cummulativeProduct)
90 {
91 this.pBody = pBody;
92 this.operationsList = operationsList;
93 this.boundVariables = boundVariables;
94 this.enforcedConstraints = new HashSet<>();
95 this.deltaVariables = deltaVariables;
96 this.cost = cost;
97 this.cummulativeProduct = cummulativeProduct;
98 }
99
100 // NOT included for EXTEND: bind all variables of op
101 private void accountNewOperation(PConstraintInfo constraintInfo) {
102 this.enforcedConstraints.add(constraintInfo.getConstraint());
103 accountCost(constraintInfo);
104 }
105
106 private void accountCost(PConstraintInfo constraintInfo) {
107 double constraintCost = constraintInfo.getCost();
108 double branchFactor = constraintCost;
109 if (constraintCost > 0){
110 cost += cummulativeProduct * constraintCost;
111 cummulativeProduct *= branchFactor;
112 }
113 }
114
115
116 public Set<PConstraint> getEnforcedConstraints() {
117 return enforcedConstraints;
118 }
119
120 /**
121 * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded),
122 * immediately applicable ones (saved as presently viable extends),
123 * and not yet applicable ones (discarded).
124 *
125 * @param allPotentialExtendInfos all other extends that may be applicable
126 * to this plan state now or in the future;
127 * MUST consist of "extend" constraint applications only (at least one free variable)
128 */
129 public void updateExtends(Iterable<PConstraintInfo> allPotentialExtendInfos) {
130 presentExtends = new ArrayList<>();
131
132
133 // categorize future/present extend constraint infos
134 for (PConstraintInfo op : allPotentialExtendInfos) {
135 updateExtendInternal(op);
136 }
137 }
138
139 /**
140 * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded),
141 * immediately applicable ones (saved as presently viable extends),
142 * and not yet applicable ones (discarded).
143 *
144 * @param extendOpsByBoundVariables all EXTEND operations indexed by affected <i>bound</i> variables
145 * MUST consist of "extend" constraint applications only (at least one free variable)
146 */
147 public void updateExtendsBasedOnDelta(
148 Iterable<PConstraintInfo> previousPresentExtends,
149 Map<PVariable, ? extends Collection<PConstraintInfo>> extendOpsByBoundVariables)
150 {
151 presentExtends = new ArrayList<>();
152 if (operationsList.isEmpty())
153 throw new IllegalStateException("Not applicable as starting step");
154
155 for (PConstraintInfo extend: previousPresentExtends) {
156 updateExtendInternal(extend);
157 }
158
159 Set<PConstraintInfo> affectedExtends = new HashSet<>();
160 for (PVariable variable : deltaVariables) {
161 // only those check ops may become applicable that have an affected variable in the delta
162 Collection<PConstraintInfo> extendsForVariable = extendOpsByBoundVariables.get(variable);
163 if (null != extendsForVariable) {
164 affectedExtends.addAll(extendsForVariable);
165 }
166 }
167 for (PConstraintInfo extend: affectedExtends) {
168 updateExtendInternal(extend);
169 }
170 }
171
172 private void updateExtendInternal(PConstraintInfo op) {
173 if(!enforcedConstraints.contains(op.getConstraint())) {
174 categorizeExtend(op);
175 }
176 }
177
178 /**
179 * Check operations that newly became applicable (see {@link #getDeltaVariables()})
180 * are appended to operations lists.
181 *
182 * <p> Will never discover degenerate checks (of PConstraints with zero variables),
183 * so must not use on initial state.
184 *
185 * @param allPotentialCheckInfos all CHECK operations
186 * MUST consist of "check" constraint applications only (no free variables)
187 * and must be iterable in decreasing order of cost
188 *
189 *
190 */
191 public void applyChecks(List<PConstraintInfo> allPotentialCheckInfos) {
192 applyChecksInternal(allPotentialCheckInfos);
193 }
194
195 /**
196 * Immediately applicable checks are appended to operations lists.
197 *
198 * @param checkOpsByVariables all CHECK operations indexed by affected variables
199 * MUST consist of "check" constraint applications only (no free variables)
200 * and each bucket must be iterable in decreasing order of cost
201 */
202 public void applyChecksBasedOnDelta(Map<PVariable, List<PConstraintInfo>> checkOpsByVariables) {
203 if (operationsList.isEmpty())
204 throw new IllegalStateException("Not applicable as starting step");
205
206 Iterable<PConstraintInfo> affectedChecks = Collections.emptyList();
207
208 for (PVariable variable : deltaVariables) {
209 // only those check ops may become applicable that have an affected variable in the delta
210 List<PConstraintInfo> checksForVariable = checkOpsByVariables.get(variable);
211 if (null != checksForVariable) {
212 affectedChecks = OrderedIterableMerge.mergeUniques(affectedChecks, checksForVariable, infoComparator);
213 }
214 }
215
216 // checks retain their order, no re-sorting needed
217 applyChecksInternal(affectedChecks);
218 }
219
220 private void applyChecksInternal(Iterable<PConstraintInfo> checks) {
221 for (PConstraintInfo checkInfo : checks) {
222 if (this.boundVariables.containsAll(checkInfo.getBoundVariables()) &&
223 !enforcedConstraints.contains(checkInfo.getConstraint()))
224 {
225 operationsList.add(checkInfo);
226 accountNewOperation(checkInfo);
227 }
228 }
229 }
230
231
232 private void categorizeExtend(PConstraintInfo constraintInfo) {
233 PConstraintCategory category = constraintInfo.getCategory(pBody, boundVariables);
234 if (category == PConstraintCategory.PRESENT) {
235 presentExtends.add(constraintInfo);
236 } else {
237 // do not categorize past/future operations
238 }
239 }
240
241
242 public PBody getAssociatedPBody() {
243 return pBody;
244 }
245
246 public List<PConstraintInfo> getOperations() {
247 return operationsList;
248 }
249
250 public Set<PVariable> getBoundVariables() {
251 return boundVariables;
252 }
253
254 /**
255 * @return the derived cost of the plan contained in the state
256 */
257 public double getCost() {
258 return cost;
259 }
260
261
262 /**
263 * @return cumulative branching factor
264 * @since 2.1
265 */
266 public double getCummulativeProduct() {
267 return cummulativeProduct;
268 }
269
270 public List<PConstraintInfo> getPresentExtends() {
271 return presentExtends;
272 }
273
274 /**
275 * Contains only those variables that are added by the newest extend
276 * (or the initially bound ones if no extend yet)
277 */
278 public Collection<PVariable> getDeltaVariables() {
279 return deltaVariables;
280 }
281
282
283}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java
new file mode 100644
index 00000000..73312dc9
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java
@@ -0,0 +1,430 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.compiler;
10
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.stream.Collectors;
18import java.util.stream.Stream;
19
20import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
21import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
22import tools.refinery.viatra.runtime.localsearch.operations.check.AggregatorCheck;
23import tools.refinery.viatra.runtime.localsearch.operations.check.BinaryTransitiveClosureCheck;
24import tools.refinery.viatra.runtime.localsearch.operations.check.CheckConstant;
25import tools.refinery.viatra.runtime.localsearch.operations.check.CheckPositivePatternCall;
26import tools.refinery.viatra.runtime.localsearch.operations.check.CountCheck;
27import tools.refinery.viatra.runtime.localsearch.operations.check.ExpressionCheck;
28import tools.refinery.viatra.runtime.localsearch.operations.check.ExpressionEvalCheck;
29import tools.refinery.viatra.runtime.localsearch.operations.check.InequalityCheck;
30import tools.refinery.viatra.runtime.localsearch.operations.check.NACOperation;
31import tools.refinery.viatra.runtime.localsearch.operations.extend.AggregatorExtend;
32import tools.refinery.viatra.runtime.localsearch.operations.extend.CountOperation;
33import tools.refinery.viatra.runtime.localsearch.operations.extend.ExpressionEval;
34import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendBinaryTransitiveClosure;
35import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendConstant;
36import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendPositivePatternCall;
37import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation;
38import tools.refinery.viatra.runtime.localsearch.planner.util.CompilerHelper;
39import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
40import tools.refinery.viatra.runtime.matchers.context.IInputKey;
41import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
42import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
43import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
44import tools.refinery.viatra.runtime.matchers.planning.operations.PApply;
45import tools.refinery.viatra.runtime.matchers.planning.operations.POperation;
46import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
47import tools.refinery.viatra.runtime.matchers.planning.operations.PStart;
48import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
49import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
50import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
51import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
52import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation;
53import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality;
54import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
55import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternMatchCounter;
56import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint;
57import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure;
58import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
59import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
60import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
61import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
62import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
63
64/**
65 * @author Zoltan Ujhelyi
66 * @since 1.7
67 *
68 */
69public abstract class AbstractOperationCompiler implements IOperationCompiler {
70
71 protected static final String UNSUPPORTED_TYPE_MESSAGE = "Unsupported type: ";
72
73 protected abstract void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping);
74
75 /**
76 * @throws ViatraQueryRuntimeException
77 */
78 protected abstract void createCheck(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping);
79
80 /**
81 * @throws ViatraQueryRuntimeException
82 */
83 protected abstract void createCheck(TypeFilterConstraint typeConstraint, Map<PVariable, Integer> variableMapping);
84
85 /**
86 * @since 2.0
87 * @throws ViatraQueryRuntimeException
88 */
89 protected abstract void createUnaryTypeCheck(IInputKey type, int position);
90
91 protected List<ISearchOperation> operations;
92 protected Set<CallWithAdornment> dependencies = new HashSet<>();
93 protected Map<PConstraint, Set<Integer>> variableBindings;
94 private Map<PVariable, Integer> variableMappings;
95 protected final IQueryRuntimeContext runtimeContext;
96
97 public AbstractOperationCompiler(IQueryRuntimeContext runtimeContext) {
98 this.runtimeContext = runtimeContext;
99 }
100
101 /**
102 * Compiles a plan of <code>POperation</code>s to a list of type <code>List&ltISearchOperation></code>
103 *
104 * @param plan
105 * @param boundParameters
106 * @return an ordered list of POperations that make up the compiled search plan
107 * @throws ViatraQueryRuntimeException
108 */
109 @Override
110 public List<ISearchOperation> compile(SubPlan plan, Set<PParameter> boundParameters) {
111
112 variableMappings = CompilerHelper.createVariableMapping(plan);
113 variableBindings = CompilerHelper.cacheVariableBindings(plan, variableMappings, boundParameters);
114
115 operations = new ArrayList<>();
116
117 List<POperation> operationList = CompilerHelper.createOperationsList(plan);
118 for (POperation pOperation : operationList) {
119 compile(pOperation, variableMappings);
120 }
121
122 return operations;
123 }
124
125 private void compile(POperation pOperation, Map<PVariable, Integer> variableMapping) {
126
127 if (pOperation instanceof PApply) {
128 PApply pApply = (PApply) pOperation;
129 PConstraint pConstraint = pApply.getPConstraint();
130
131 if (isCheck(pConstraint, variableMapping)) {
132 // check
133 createCheckDispatcher(pConstraint, variableMapping);
134 } else {
135 // extend
136 createExtendDispatcher(pConstraint, variableMapping);
137 }
138
139 } else if (pOperation instanceof PStart) {
140 // nop
141 } else if (pOperation instanceof PProject) {
142 // nop
143 } else {
144 throw new QueryProcessingException("PStart, PApply or PProject was expected, received: " + pOperation.getClass(), null,"Unexpected POperation type", null);
145 }
146
147 }
148
149 private void createCheckDispatcher(PConstraint pConstraint, Map<PVariable, Integer> variableMapping) {
150
151
152 // DeferredPConstraint subclasses
153
154 // Equalities are normalized
155
156 if (pConstraint instanceof Inequality) {
157 createCheck((Inequality) pConstraint, variableMapping);
158 } else if (pConstraint instanceof PositivePatternCall){
159 createCheck((PositivePatternCall) pConstraint, variableMapping);
160 } else if (pConstraint instanceof NegativePatternCall) {
161 createCheck((NegativePatternCall) pConstraint,variableMapping);
162 } else if (pConstraint instanceof AggregatorConstraint) {
163 createCheck((AggregatorConstraint) pConstraint, variableMapping);
164 } else if (pConstraint instanceof PatternMatchCounter) {
165 createCheck((PatternMatchCounter) pConstraint, variableMapping);
166 } else if (pConstraint instanceof ExpressionEvaluation) {
167 createCheck((ExpressionEvaluation) pConstraint, variableMapping);
168 } else if (pConstraint instanceof TypeFilterConstraint) {
169 createCheck((TypeFilterConstraint) pConstraint,variableMapping);
170 } else if (pConstraint instanceof ExportedParameter) {
171 // Nothing to do here
172 } else
173
174 // EnumerablePConstraint subclasses
175
176 if (pConstraint instanceof BinaryTransitiveClosure) {
177 createCheck((BinaryTransitiveClosure) pConstraint, variableMapping);
178 } else if (pConstraint instanceof BinaryReflexiveTransitiveClosure) {
179 createCheck((BinaryReflexiveTransitiveClosure)pConstraint, variableMapping);
180 } else if (pConstraint instanceof ConstantValue) {
181 createCheck((ConstantValue) pConstraint, variableMapping);
182 } else if (pConstraint instanceof TypeConstraint) {
183 createCheck((TypeConstraint) pConstraint,variableMapping);
184 } else {
185 String msg = "Unsupported Check constraint: "+pConstraint.toString();
186 throw new QueryProcessingException(msg, null, msg, null);
187 }
188
189 }
190
191 protected void createExtendDispatcher(PConstraint pConstraint, Map<PVariable, Integer> variableMapping) {
192
193 // DeferredPConstraint subclasses
194
195 // Equalities are normalized
196 if (pConstraint instanceof PositivePatternCall) {
197 createExtend((PositivePatternCall)pConstraint, variableMapping);
198 } else if (pConstraint instanceof AggregatorConstraint) {
199 createExtend((AggregatorConstraint) pConstraint, variableMapping);
200 } else if (pConstraint instanceof PatternMatchCounter) {
201 createExtend((PatternMatchCounter) pConstraint, variableMapping);
202 } else if (pConstraint instanceof ExpressionEvaluation) {
203 createExtend((ExpressionEvaluation) pConstraint, variableMapping);
204 } else if (pConstraint instanceof ExportedParameter) {
205 // ExportedParameters are compiled to NOP
206 } else
207
208 // EnumerablePConstraint subclasses
209
210 if (pConstraint instanceof ConstantValue) {
211 createExtend((ConstantValue) pConstraint, variableMapping);
212 } else if (pConstraint instanceof TypeConstraint) {
213 createExtend((TypeConstraint) pConstraint, variableMapping);
214 } else if (pConstraint instanceof BinaryTransitiveClosure) {
215 createExtend((BinaryTransitiveClosure)pConstraint, variableMapping);
216 } else if (pConstraint instanceof BinaryReflexiveTransitiveClosure) {
217 createExtend((BinaryReflexiveTransitiveClosure)pConstraint, variableMapping);
218 } else {
219 String msg = "Unsupported Extend constraint: "+pConstraint.toString();
220 throw new QueryProcessingException(msg, null, msg, null);
221 }
222 }
223
224 private boolean isCheck(PConstraint pConstraint, final Map<PVariable, Integer> variableMapping) {
225 if (pConstraint instanceof NegativePatternCall){
226 return true;
227 }else if (pConstraint instanceof PositivePatternCall){
228 // Positive pattern call is check if all non-single used variables are bound
229 List<Integer> callVariables = pConstraint.getAffectedVariables().stream()
230 .filter(input -> input.getReferringConstraints().size() > 1)
231 .map(variableMapping::get)
232 .collect(Collectors.toList());
233 return variableBindings.get(pConstraint).containsAll(callVariables);
234 }else if (pConstraint instanceof AggregatorConstraint){
235 PVariable outputvar = ((AggregatorConstraint) pConstraint).getResultVariable();
236 return variableBindings.get(pConstraint).contains(variableMapping.get(outputvar));
237 }else if (pConstraint instanceof PatternMatchCounter){
238 PVariable outputvar = ((PatternMatchCounter) pConstraint).getResultVariable();
239 return variableBindings.get(pConstraint).contains(variableMapping.get(outputvar));
240 }else if (pConstraint instanceof ExpressionEvaluation){
241 PVariable outputvar = ((ExpressionEvaluation) pConstraint).getOutputVariable();
242 return outputvar == null || variableBindings.get(pConstraint).contains(variableMapping.get(outputvar));
243 } else {
244 // In other cases, all variables shall be bound to be a check
245 Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
246 Set<Integer> varIndices = new HashSet<>();
247 for (PVariable variable : affectedVariables) {
248 varIndices.add(variableMapping.get(variable));
249 }
250 return variableBindings.get(pConstraint).containsAll(varIndices);
251 }
252 }
253
254 @Override
255 public Set<CallWithAdornment> getDependencies() {
256 return dependencies;
257 }
258
259 /**
260 * @return the cached variable bindings for the previously created plan
261 */
262 @Override
263 public Map<PVariable, Integer> getVariableMappings() {
264 return variableMappings;
265 }
266
267 protected void createCheck(PatternMatchCounter counter, Map<PVariable, Integer> variableMapping) {
268 CallInformation information = CallInformation.create(counter, variableMapping, variableBindings.get(counter));
269 operations.add(new CountCheck(information, variableMapping.get(counter.getResultVariable())));
270 dependencies.add(information.getCallWithAdornment());
271 }
272
273 protected void createCheck(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) {
274 CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall));
275 operations.add(new CheckPositivePatternCall(information));
276 dependencies.add(information.getCallWithAdornment());
277 }
278
279 protected void createCheck(ConstantValue constant, Map<PVariable, Integer> variableMapping) {
280 int position = variableMapping.get(constant.getVariablesTuple().get(0));
281 operations.add(new CheckConstant(position, constant.getSupplierKey()));
282 }
283
284 protected void createCheck(BinaryTransitiveClosure binaryTransitiveClosure, Map<PVariable, Integer> variableMapping) {
285 int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0));
286 int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1));
287
288 //The second parameter is NOT bound during execution!
289 CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, Stream.of(sourcePosition).collect(Collectors.toSet()));
290 operations.add(new BinaryTransitiveClosureCheck(information, sourcePosition, targetPosition, false));
291 dependencies.add(information.getCallWithAdornment());
292 }
293
294 /**
295 * @since 2.0
296 */
297 protected void createCheck(BinaryReflexiveTransitiveClosure binaryTransitiveClosure, Map<PVariable, Integer> variableMapping) {
298 int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0));
299 int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1));
300
301 //The second parameter is NOT bound during execution!
302 CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, Stream.of(sourcePosition).collect(Collectors.toSet()));
303 createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), sourcePosition);
304 operations.add(new BinaryTransitiveClosureCheck(information, sourcePosition, targetPosition, true));
305 dependencies.add(information.getCallWithAdornment());
306 }
307
308 protected void createCheck(ExpressionEvaluation expressionEvaluation, Map<PVariable, Integer> variableMapping) {
309 // Fill unbound variables with null; simply copy all variables. Unbound variables will be null anyway
310 Iterable<String> inputParameterNames = expressionEvaluation.getEvaluator().getInputParameterNames();
311 Map<String, Integer> nameMap = new HashMap<>();
312
313 for (String pVariableName : inputParameterNames) {
314 PVariable pVariable = expressionEvaluation.getPSystem().getVariableByNameChecked(pVariableName);
315 nameMap.put(pVariableName, variableMapping.get(pVariable));
316 }
317
318 // output variable can be null; if null it is an ExpressionCheck
319 if(expressionEvaluation.getOutputVariable() == null){
320 operations.add(new ExpressionCheck(expressionEvaluation.getEvaluator(), nameMap));
321 } else {
322 operations.add(new ExpressionEvalCheck(expressionEvaluation.getEvaluator(), nameMap, expressionEvaluation.isUnwinding(), variableMapping.get(expressionEvaluation.getOutputVariable())));
323 }
324 }
325
326 protected void createCheck(AggregatorConstraint aggregator, Map<PVariable, Integer> variableMapping) {
327 CallInformation information = CallInformation.create(aggregator, variableMapping, variableBindings.get(aggregator));
328 operations.add(new AggregatorCheck(information, aggregator, variableMapping.get(aggregator.getResultVariable())));
329 dependencies.add(information.getCallWithAdornment());
330 }
331
332 protected void createCheck(NegativePatternCall negativePatternCall, Map<PVariable, Integer> variableMapping) {
333 CallInformation information = CallInformation.create(negativePatternCall, variableMapping, variableBindings.get(negativePatternCall));
334 operations.add(new NACOperation(information));
335 dependencies.add(information.getCallWithAdornment());
336 }
337
338 protected void createCheck(Inequality inequality, Map<PVariable, Integer> variableMapping) {
339 operations.add(new InequalityCheck(variableMapping.get(inequality.getWho()), variableMapping.get(inequality.getWithWhom())));
340 }
341
342 protected void createExtend(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) {
343 CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall));
344 operations.add(new ExtendPositivePatternCall(information));
345 dependencies.add(information.getCallWithAdornment());
346 }
347
348 protected void createExtend(BinaryTransitiveClosure binaryTransitiveClosure, Map<PVariable, Integer> variableMapping) {
349 int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0));
350 int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1));
351
352 boolean sourceBound = variableBindings.get(binaryTransitiveClosure).contains(sourcePosition);
353 boolean targetBound = variableBindings.get(binaryTransitiveClosure).contains(targetPosition);
354
355 CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, variableBindings.get(binaryTransitiveClosure));
356
357 if (sourceBound && !targetBound) {
358 operations.add(new ExtendBinaryTransitiveClosure.Forward(information, sourcePosition, targetPosition, false));
359 dependencies.add(information.getCallWithAdornment());
360 } else if (!sourceBound && targetBound) {
361 operations.add(new ExtendBinaryTransitiveClosure.Backward(information, sourcePosition, targetPosition, false));
362 dependencies.add(information.getCallWithAdornment());
363 } else {
364 String msg = "Binary transitive closure not supported with two unbound parameters";
365 throw new QueryProcessingException(msg, null, msg, binaryTransitiveClosure.getPSystem().getPattern());
366 }
367 }
368
369 /**
370 * @since 2.0
371 */
372 protected void createExtend(BinaryReflexiveTransitiveClosure binaryTransitiveClosure, Map<PVariable, Integer> variableMapping) {
373 int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0));
374 int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1));
375
376 boolean sourceBound = variableBindings.get(binaryTransitiveClosure).contains(sourcePosition);
377 boolean targetBound = variableBindings.get(binaryTransitiveClosure).contains(targetPosition);
378
379 CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, variableBindings.get(binaryTransitiveClosure));
380
381 if (sourceBound && !targetBound) {
382 createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), sourcePosition);
383 operations.add(new ExtendBinaryTransitiveClosure.Forward(information, sourcePosition, targetPosition, true));
384 dependencies.add(information.getCallWithAdornment());
385 } else if (!sourceBound && targetBound) {
386 createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), targetPosition);
387 operations.add(new ExtendBinaryTransitiveClosure.Backward(information, sourcePosition, targetPosition, true));
388 dependencies.add(information.getCallWithAdornment());
389 } else {
390 String msg = "Binary reflective transitive closure not supported with two unbound parameters";
391 throw new QueryProcessingException(msg, null, msg, binaryTransitiveClosure.getPSystem().getPattern());
392 }
393 }
394
395 protected void createExtend(ConstantValue constant, Map<PVariable, Integer> variableMapping) {
396 int position = variableMapping.get(constant.getVariablesTuple().get(0));
397 operations.add(new ExtendConstant(position, constant.getSupplierKey()));
398 }
399
400 protected void createExtend(ExpressionEvaluation expressionEvaluation, Map<PVariable, Integer> variableMapping) {
401 // Fill unbound variables with null; simply copy all variables. Unbound variables will be null anyway
402 Iterable<String> inputParameterNames = expressionEvaluation.getEvaluator().getInputParameterNames();
403 Map<String, Integer> nameMap = new HashMap<>();
404
405 for (String pVariableName : inputParameterNames) {
406 PVariable pVariable = expressionEvaluation.getPSystem().getVariableByNameChecked(pVariableName);
407 nameMap.put(pVariableName, variableMapping.get(pVariable));
408 }
409
410 // output variable can be null; if null it is an ExpressionCheck
411 if(expressionEvaluation.getOutputVariable() == null){
412 operations.add(new ExpressionCheck(expressionEvaluation.getEvaluator(), nameMap));
413 } else {
414 operations.add(new ExpressionEval(expressionEvaluation.getEvaluator(), nameMap, expressionEvaluation.isUnwinding(), variableMapping.get(expressionEvaluation.getOutputVariable())));
415 }
416 }
417
418 protected void createExtend(AggregatorConstraint aggregator, Map<PVariable, Integer> variableMapping) {
419 CallInformation information = CallInformation.create(aggregator, variableMapping, variableBindings.get(aggregator));
420 operations.add(new AggregatorExtend(information, aggregator, variableMapping.get(aggregator.getResultVariable())));
421 dependencies.add(information.getCallWithAdornment());
422 }
423
424 protected void createExtend(PatternMatchCounter patternMatchCounter, Map<PVariable, Integer> variableMapping) {
425 CallInformation information = CallInformation.create(patternMatchCounter, variableMapping, variableBindings.get(patternMatchCounter));
426 operations.add(new CountOperation(information, variableMapping.get(patternMatchCounter.getResultVariable())));
427 dependencies.add(information.getCallWithAdornment());
428 }
429
430} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java
new file mode 100644
index 00000000..d86982e9
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.localsearch.planner.compiler;
11
12import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeCheck;
13import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtend;
14import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue;
15import tools.refinery.viatra.runtime.matchers.context.IInputKey;
16import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
17import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
18import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
21import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
22
23import java.util.*;
24
25/**
26 * @author Zoltan Ujhelyi
27 * @since 1.7
28 *
29 */
30public class GenericOperationCompiler extends AbstractOperationCompiler {
31
32 public GenericOperationCompiler(IQueryRuntimeContext runtimeContext) {
33 super(runtimeContext);
34 }
35
36 @Override
37 protected void createCheck(TypeFilterConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
38 IInputKey inputKey = typeConstraint.getInputKey();
39 Tuple tuple = typeConstraint.getVariablesTuple();
40 int[] positions = new int[tuple.getSize()];
41 for (int i = 0; i < tuple.getSize(); i++) {
42 PVariable variable = (PVariable) tuple.get(i);
43 positions[i] = variableMapping.get(variable);
44 }
45 operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(variableMapping.size(), positions)));
46
47 }
48
49 @Override
50 protected void createCheck(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
51 IInputKey inputKey = typeConstraint.getSupplierKey();
52 Tuple tuple = typeConstraint.getVariablesTuple();
53 int[] positions = new int[tuple.getSize()];
54 for (int i = 0; i < tuple.getSize(); i++) {
55 PVariable variable = (PVariable) tuple.get(i);
56 positions[i] = variableMapping.get(variable);
57 }
58 operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(variableMapping.size(), positions)));
59 }
60
61 @Override
62 protected void createUnaryTypeCheck(IInputKey inputKey, int position) {
63 int[] positions = new int[] {position};
64 operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(1, positions)));
65 }
66
67 @Override
68 protected void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
69 IInputKey inputKey = typeConstraint.getSupplierKey();
70 Tuple tuple = typeConstraint.getVariablesTuple();
71
72 int[] positions = new int[tuple.getSize()];
73 List<Integer> boundVariableIndices = new ArrayList<>();
74 List<Integer> boundVariables = new ArrayList<>();
75 Set<Integer> unboundVariables = new HashSet<>();
76 for (int i = 0; i < tuple.getSize(); i++) {
77 PVariable variable = (PVariable) tuple.get(i);
78 Integer position = variableMapping.get(variable);
79 positions[i] = position;
80 if (variableBindings.get(typeConstraint).contains(position)) {
81 boundVariableIndices.add(i);
82 boundVariables.add(position);
83 } else {
84 unboundVariables.add(position);
85 }
86 }
87 TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices);
88 TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables);
89 // If multiple tuple elements from the indexer should be bound to the same variable, we must use a
90 // {@link GenericTypeExtend} check whether the tuple elements have the same value.
91 if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) {
92 operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask, unboundVariables.iterator().next()));
93 } else {
94 operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables));
95 }
96
97 }
98
99
100
101}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java
new file mode 100644
index 00000000..625a7eb2
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java
@@ -0,0 +1,53 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.compiler;
10
11import java.util.List;
12import java.util.Map;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
16import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
17import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
18import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
19import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
22
23/**
24 * An operation compiler is responsible for creating executable search plans from the subplan structure.
25 *
26 * @author Zoltan Ujhelyi
27 * @since 1.7
28 *
29 */
30public interface IOperationCompiler {
31
32 /**
33 * Compiles a plan of <code>POperation</code>s to a list of type <code>List&ltISearchOperation></code>
34 *
35 * @param plan
36 * @param boundParameters
37 * @return an ordered list of POperations that make up the compiled search plan
38 * @throws ViatraQueryRuntimeException
39 */
40 List<ISearchOperation> compile(SubPlan plan, Set<PParameter> boundParameters);
41
42 /**
43 * Replaces previous method returning {@link MatcherReference}
44 * @since 2.1
45 */
46 Set<CallWithAdornment> getDependencies();
47
48 /**
49 * @return the cached variable bindings for the previously created plan
50 */
51 Map<PVariable, Integer> getVariableMappings();
52
53} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java
new file mode 100644
index 00000000..9b44612b
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java
@@ -0,0 +1,63 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.cost;
10
11import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
12import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess;
13import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
14import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
15import java.util.Collection;
16import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
17import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
18
19/**
20 * This interface denotes the evaluation context of a constraint, intended for cost estimation. Provides access to information
21 * on which the cost function can base its calculation.
22 *
23 * @author Grill Balázs
24 * @since 1.4
25 * @noimplement
26 */
27public interface IConstraintEvaluationContext {
28
29 /**
30 * Get the constraint to be evaluated
31 */
32 public PConstraint getConstraint();
33
34 /**
35 * Unbound variables at the time of evaluating the constraint
36 */
37 public Collection<PVariable> getFreeVariables();
38
39 /**
40 * Bound variables at the time of evaluating the constraint
41 */
42 public Collection<PVariable> getBoundVariables();
43
44 public IQueryRuntimeContext getRuntimeContext();
45
46 /**
47 * @since 1.5
48 */
49 public QueryAnalyzer getQueryAnalyzer();
50
51 /**
52 * @deprecated use {@link #resultProviderRequestor()}
53 * @since 1.5
54 */
55 @Deprecated
56 public IQueryResultProviderAccess resultProviderAccess();
57
58 /**
59 * @since 2.1
60 */
61 public ResultProviderRequestor resultProviderRequestor();
62
63}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java
new file mode 100644
index 00000000..4d9d0708
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.cost;
10
11/**
12 * Common interface for cost function implementation
13 *
14 * @author Grill Balázs
15 * @since 1.4
16 *
17 */
18public interface ICostFunction{
19
20 public double apply(IConstraintEvaluationContext input);
21
22}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java
new file mode 100644
index 00000000..df9292f0
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java
@@ -0,0 +1,91 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.cost.impl;
10
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15import java.util.stream.Collectors;
16
17import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
18import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
19import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
22import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
25
26/**
27 * This cost function is intended to be used on hybrid configuration, with the strict restriction than any
28 * non-flattened positive pattern call is executed with Rete engine. This implementation provides the exact number
29 * of matches by invoking the result provider for the called pattern.
30 *
31 * @deprecated {@link StatisticsBasedConstraintCostFunction} should use {@link IQueryResultProvider#estimateCardinality(tools.refinery.viatra.runtime.matchers.tuple.TupleMask, org.eclipse.viatra.query.runtime.matchers.util.Accuracy)}
32 */
33@Deprecated
34public class HybridMatcherConstraintCostFunction extends IndexerBasedConstraintCostFunction {
35
36 @Override
37 protected double _calculateCost(PositivePatternCall patternCall, IConstraintEvaluationContext context) {
38 // Determine local constant constraints which is used to filter results
39 Tuple variables = patternCall.getVariablesTuple();
40 Set<Object> variablesSet = variables.getDistinctElements();
41 final Map<PVariable, Object> constantMap = new HashMap<>();
42 for (PConstraint _constraint : patternCall.getPSystem().getConstraints()) {
43 if (_constraint instanceof ConstantValue){
44 ConstantValue constraint = (ConstantValue) _constraint;
45 PVariable variable = (PVariable) constraint.getVariablesTuple().get(0);
46 if (variablesSet.contains(variable) && context.getBoundVariables().contains(variable)) {
47 constantMap.put(variable, constraint.getSupplierKey());
48 }
49 }
50 }
51
52 // Determine filter
53 Object[] filter = new Object[variables.getSize()];
54 for(int i=0; i < variables.getSize(); i++){
55 filter[i] = constantMap.get(variables.get(i));
56 }
57
58 // aggregate keys are the bound and not filtered variables
59 // These will be fixed in runtime, but unknown at planning time
60 // This is represented by indices to ease working with result tuples
61 final Map<Object, Integer> variableIndices = variables.invertIndex();
62 List<Integer> aggregateKeys = context.getBoundVariables().stream()
63 .filter(input -> !constantMap.containsKey(input))
64 .map(variableIndices::get)
65 .collect(Collectors.toList());
66
67 IQueryResultProvider resultProvider = context.resultProviderRequestor().requestResultProvider(patternCall, null);
68 Map<Tuple, Integer> aggregatedCounts = new HashMap<>();
69
70 // Iterate over all matches and count together matches that has equal values on
71 // aggregateKeys positions. The cost of the pattern call is considered to be the
72 // Maximum of these counted values
73
74 int result = 0;
75 // NOTE: a stream is not an iterable (cannot be iterated more than once), so to use it in a for-loop
76 // it has to be wrapped; in the following line a lambda is used to implement Iterable#iterator()
77 for (Tuple match : (Iterable<Tuple>) () -> resultProvider.getAllMatches(filter).iterator()) {
78 Tuple extracted = Tuples.flatTupleOf(aggregateKeys.stream().map(match::get).toArray());
79 int count = (aggregatedCounts.containsKey(extracted))
80 ? aggregatedCounts.get(extracted) + 1
81 : 1;
82 aggregatedCounts.put(extracted, count);
83 if (result < count) {
84 result = count;
85 }
86 }
87
88 return result;
89 }
90
91} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java
new file mode 100644
index 00000000..9e2c8680
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java
@@ -0,0 +1,49 @@
1/**
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 */
9package tools.refinery.viatra.runtime.localsearch.planner.cost.impl;
10
11import java.util.Optional;
12
13import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
14import tools.refinery.viatra.runtime.matchers.context.IInputKey;
15import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
16import tools.refinery.viatra.runtime.matchers.util.Accuracy;
17
18/**
19 * Cost function which calculates cost based on the cardinality of items in the runtime model, provided by the base indexer
20 *
21 * @author Grill Balázs
22 * @since 1.4
23 */
24public class IndexerBasedConstraintCostFunction extends StatisticsBasedConstraintCostFunction {
25
26
27
28
29 /**
30 *
31 */
32 public IndexerBasedConstraintCostFunction() {
33 super();
34 }
35
36 /**
37 * @param inverseNavigationPenalty
38 * @since 2.1
39 */
40 public IndexerBasedConstraintCostFunction(double inverseNavigationPenalty) {
41 super(inverseNavigationPenalty);
42 }
43
44 @Override
45 public Optional<Long> projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) {
46 return input.getRuntimeContext().estimateCardinality(supplierKey, groupMask, requiredAccuracy);
47 }
48
49}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java
new file mode 100644
index 00000000..873be31d
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java
@@ -0,0 +1,413 @@
1/**
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 */
9package tools.refinery.viatra.runtime.localsearch.planner.cost.impl;
10
11import static tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper.min;
12
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18import java.util.Map;
19import java.util.Optional;
20import java.util.Set;
21
22import tools.refinery.viatra.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
23import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
24import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction;
25import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
26import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
27import tools.refinery.viatra.runtime.matchers.context.IInputKey;
28import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper;
29import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
30import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
31import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
32import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
33import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
34import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
35import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation;
36import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality;
37import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
38import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternMatchCounter;
39import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint;
40import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure;
41import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
42import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
43import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
44import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
45import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
46import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
47import tools.refinery.viatra.runtime.matchers.util.Accuracy;
48import tools.refinery.viatra.runtime.matchers.util.Preconditions;
49
50/**
51 * Cost function which calculates cost based on the cardinality of items in the runtime model
52 *
53 * <p> To provide custom statistics, override
54 * {@link #projectionSize(IConstraintEvaluationContext, IInputKey, TupleMask, Accuracy)}
55 * and {@link #bucketSize(IQueryReference, IConstraintEvaluationContext, TupleMask)}.
56 *
57 * @author Grill Balázs
58 * @since 1.4
59 */
60public abstract class StatisticsBasedConstraintCostFunction implements ICostFunction {
61 protected static final double MAX_COST = 250.0;
62
63 protected static final double DEFAULT_COST = StatisticsBasedConstraintCostFunction.MAX_COST - 100.0;
64
65 /**
66 * @since 2.1
67 */
68 public static final double INVERSE_NAVIGATION_PENALTY_DEFAULT = 0.10;
69 /**
70 * @since 2.1
71 */
72 public static final double INVERSE_NAVIGATION_PENALTY_GENERIC = 0.01;
73 /**
74 * @since 2.7
75 */
76 public static final double EVAL_UNWIND_EXTENSION_FACTOR = 3.0;
77
78 private final double inverseNavigationPenalty;
79
80
81 /**
82 * @since 2.1
83 */
84 public StatisticsBasedConstraintCostFunction(double inverseNavigationPenalty) {
85 super();
86 this.inverseNavigationPenalty = inverseNavigationPenalty;
87 }
88 public StatisticsBasedConstraintCostFunction() {
89 this(INVERSE_NAVIGATION_PENALTY_DEFAULT);
90 }
91
92 /**
93 * @deprecated call and implement {@link #projectionSize(IConstraintEvaluationContext, IInputKey, TupleMask, Accuracy)} instead
94 */
95 @Deprecated
96 public long countTuples(final IConstraintEvaluationContext input, final IInputKey supplierKey) {
97 return projectionSize(input, supplierKey, TupleMask.identity(supplierKey.getArity()), Accuracy.EXACT_COUNT).orElse(-1L);
98 }
99
100 /**
101 * Override this to provide custom statistics on edge/node counts.
102 * New implementors shall implement this instead of {@link #countTuples(IConstraintEvaluationContext, IInputKey)}
103 * @since 2.1
104 */
105 public Optional<Long> projectionSize(final IConstraintEvaluationContext input, final IInputKey supplierKey,
106 final TupleMask groupMask, Accuracy requiredAccuracy) {
107 long legacyCount = countTuples(input, supplierKey);
108 return legacyCount < 0 ? Optional.empty() : Optional.of(legacyCount);
109 }
110
111 /**
112 * Override this to provide custom estimates for match set sizes of called patterns.
113 * @since 2.1
114 */
115 public Optional<Double> bucketSize(final IQueryReference patternCall,
116 final IConstraintEvaluationContext input, TupleMask projMask) {
117 IQueryResultProvider resultProvider = input.resultProviderRequestor().requestResultProvider(patternCall, null);
118 // TODO hack: use LS cost instead of true bucket size estimate
119 if (resultProvider instanceof AbstractLocalSearchResultProvider) {
120 double estimatedCost = ((AbstractLocalSearchResultProvider) resultProvider).estimateCost(projMask);
121 return Optional.of(estimatedCost);
122 } else {
123 return resultProvider.estimateAverageBucketSize(projMask, Accuracy.APPROXIMATION);
124 }
125 }
126
127
128
129 @Override
130 public double apply(final IConstraintEvaluationContext input) {
131 return this.calculateCost(input.getConstraint(), input);
132 }
133
134 protected double _calculateCost(final ConstantValue constant, final IConstraintEvaluationContext input) {
135 return 0.0f;
136 }
137
138 protected double _calculateCost(final TypeConstraint constraint, final IConstraintEvaluationContext input) {
139 final Collection<PVariable> freeMaskVariables = input.getFreeVariables();
140 final Collection<PVariable> boundMaskVariables = input.getBoundVariables();
141 IInputKey supplierKey = constraint.getSupplierKey();
142 long arity = supplierKey.getArity();
143
144 if ((arity == 1)) {
145 // unary constraint
146 return calculateUnaryConstraintCost(constraint, input);
147 } else if ((arity == 2)) {
148 // binary constraint
149 PVariable srcVariable = ((PVariable) constraint.getVariablesTuple().get(0));
150 PVariable dstVariable = ((PVariable) constraint.getVariablesTuple().get(1));
151 boolean isInverse = false;
152 // Check if inverse navigation is needed along the edge
153 if ((freeMaskVariables.contains(srcVariable) && boundMaskVariables.contains(dstVariable))) {
154 isInverse = true;
155 }
156 double binaryExtendCost = calculateBinaryCost(supplierKey, srcVariable, dstVariable, isInverse, input);
157 // Make inverse navigation slightly more expensive than forward navigation
158 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=501078
159 return (isInverse) ? binaryExtendCost + inverseNavigationPenalty : binaryExtendCost;
160 } else {
161 // n-ary constraint
162 throw new UnsupportedOperationException("Cost calculation for arity " + arity + " is not implemented yet");
163 }
164 }
165
166
167 /**
168 * @deprecated use/implement {@link #calculateBinaryCost(IInputKey, PVariable, PVariable, boolean, IConstraintEvaluationContext)} instead
169 */
170 @Deprecated
171 protected double calculateBinaryExtendCost(final IInputKey supplierKey, final PVariable srcVariable,
172 final PVariable dstVariable, final boolean isInverse, long edgeCount /* TODO remove */,
173 final IConstraintEvaluationContext input) {
174 throw new UnsupportedOperationException();
175 }
176
177 /**
178 * @since 2.1
179 */
180 protected double calculateBinaryCost(final IInputKey supplierKey, final PVariable srcVariable,
181 final PVariable dstVariable, final boolean isInverse,
182 final IConstraintEvaluationContext input) {
183 final Collection<PVariable> freeMaskVariables = input.getFreeVariables();
184 final PConstraint constraint = input.getConstraint();
185
186// IQueryMetaContext metaContext = input.getRuntimeContext().getMetaContext();
187// Collection<InputKeyImplication> implications = metaContext.getImplications(supplierKey);
188
189 Optional<Long> edgeUpper = projectionSize(input, supplierKey, TupleMask.identity(2), Accuracy.BEST_UPPER_BOUND);
190 Optional<Long> srcUpper = projectionSize(input, supplierKey, TupleMask.selectSingle(0, 2), Accuracy.BEST_UPPER_BOUND);
191 Optional<Long> dstUpper = projectionSize(input, supplierKey, TupleMask.selectSingle(1, 2), Accuracy.BEST_UPPER_BOUND);
192
193 if (freeMaskVariables.contains(srcVariable) && freeMaskVariables.contains(dstVariable)) {
194 Double branchCount = edgeUpper.map(Long::doubleValue).orElse(
195 srcUpper.map(Long::doubleValue).orElse(DEFAULT_COST)
196 *
197 dstUpper.map(Long::doubleValue).orElse(DEFAULT_COST)
198 );
199 return branchCount;
200
201 } else {
202
203 Optional<Long> srcLower = projectionSize(input, supplierKey, TupleMask.selectSingle(0, 2), Accuracy.APPROXIMATION);
204 Optional<Long> dstLower = projectionSize(input, supplierKey, TupleMask.selectSingle(1, 2), Accuracy.APPROXIMATION);
205
206 List<Optional<Long>> nodeLower = Arrays.asList(srcLower, dstLower);
207 List<Optional<Long>> nodeUpper = Arrays.asList(srcUpper, dstUpper);
208
209 int from = isInverse ? 1 : 0;
210 int to = isInverse ? 0 : 1;
211
212 Optional<Double> costEstimate = Optional.empty();
213
214 if (!freeMaskVariables.contains(srcVariable) && !freeMaskVariables.contains(dstVariable)) {
215 // both variables bound, this is a simple check
216 costEstimate = min(costEstimate, 0.9);
217 } // TODO use bucket size estimation in the runtime context
218 costEstimate = min(costEstimate,
219 edgeUpper.flatMap(edges ->
220 nodeLower.get(from).map(fromNodes ->
221 // amortize edges over start nodes
222 (fromNodes == 0) ? 0.0 : (((double) edges) / fromNodes)
223 )));
224 if (navigatesThroughFunctionalDependencyInverse(input, constraint)) {
225 costEstimate = min(costEstimate, nodeUpper.get(to).flatMap(toNodes ->
226 nodeLower.get(from).map(fromNodes ->
227 // due to a reverse functional dependency, the destination count is an upper bound for the edge count
228 (fromNodes == 0) ? 0.0 : ((double) toNodes) / fromNodes
229 )));
230 }
231 if (! edgeUpper.isPresent()) {
232 costEstimate = min(costEstimate, nodeUpper.get(to).flatMap(toNodes ->
233 nodeLower.get(from).map(fromNodes ->
234 // If count is 0, no such element exists in the model, so there will be no branching
235 // TODO rethink, why dstNodeCount / srcNodeCount instead of dstNodeCount?
236 // The universally valid bound would be something like sparseEdgeEstimate = dstNodeCount + 1.0
237 // If we assume sparseness, we can reduce it by a SPARSENESS_FACTOR (e.g. 0.1).
238 // Alternatively, discount dstNodeCount * srcNodeCount on a SPARSENESS_EXPONENT (e.g 0.75) and then amortize over srcNodeCount.
239 fromNodes != 0 ? Math.max(1.0, ((double) toNodes) / fromNodes) : 1.0
240 )));
241 }
242 if (navigatesThroughFunctionalDependency(input, constraint)) {
243 // At most one destination value
244 costEstimate = min(costEstimate, 1.0);
245 }
246
247 return costEstimate.orElse(DEFAULT_COST);
248
249 }
250 }
251
252 /**
253 * @since 1.7
254 */
255 protected boolean navigatesThroughFunctionalDependency(final IConstraintEvaluationContext input,
256 final PConstraint constraint) {
257 return navigatesThroughFunctionalDependency(input, constraint, input.getBoundVariables(), input.getFreeVariables());
258 }
259 /**
260 * @since 2.1
261 */
262 protected boolean navigatesThroughFunctionalDependencyInverse(final IConstraintEvaluationContext input,
263 final PConstraint constraint) {
264 return navigatesThroughFunctionalDependency(input, constraint, input.getFreeVariables(), input.getBoundVariables());
265 }
266 /**
267 * @since 2.1
268 */
269 protected boolean navigatesThroughFunctionalDependency(final IConstraintEvaluationContext input,
270 final PConstraint constraint, Collection<PVariable> determining, Collection<PVariable> determined) {
271 final QueryAnalyzer queryAnalyzer = input.getQueryAnalyzer();
272 final Map<Set<PVariable>, Set<PVariable>> functionalDependencies = queryAnalyzer
273 .getFunctionalDependencies(Collections.singleton(constraint), false);
274 final Set<PVariable> impliedVariables = FunctionalDependencyHelper.closureOf(determining,
275 functionalDependencies);
276 return ((impliedVariables != null) && impliedVariables.containsAll(determined));
277 }
278
279 protected double calculateUnaryConstraintCost(final TypeConstraint constraint,
280 final IConstraintEvaluationContext input) {
281 PVariable variable = (PVariable) constraint.getVariablesTuple().get(0);
282 if (input.getBoundVariables().contains(variable)) {
283 return 0.9;
284 } else {
285 return projectionSize(input, constraint.getSupplierKey(), TupleMask.identity(1), Accuracy.APPROXIMATION)
286 .map(count -> 1.0 + count).orElse(DEFAULT_COST);
287 }
288 }
289
290 protected double _calculateCost(final ExportedParameter exportedParam, final IConstraintEvaluationContext input) {
291 return 0.0;
292 }
293
294 protected double _calculateCost(final TypeFilterConstraint exportedParam,
295 final IConstraintEvaluationContext input) {
296 return 0.0;
297 }
298
299 protected double _calculateCost(final PositivePatternCall patternCall, final IConstraintEvaluationContext input) {
300 final List<Integer> boundPositions = new ArrayList<>();
301 final List<PParameter> parameters = patternCall.getReferredQuery().getParameters();
302 for (int i = 0; (i < parameters.size()); i++) {
303 final PVariable variable = patternCall.getVariableInTuple(i);
304 if (input.getBoundVariables().contains(variable)) boundPositions.add(i);
305 }
306 TupleMask projMask = TupleMask.fromSelectedIndices(parameters.size(), boundPositions);
307
308 return bucketSize(patternCall, input, projMask).orElse(DEFAULT_COST);
309 }
310
311
312 /**
313 * @since 1.7
314 */
315 protected double _calculateCost(final ExpressionEvaluation evaluation, final IConstraintEvaluationContext input) {
316 // Even if there are multiple results here, if all output variable is bound eval unwind will not result in
317 // multiple branches in search graph
318 final double multiplier = evaluation.isUnwinding() && !input.getFreeVariables().isEmpty()
319 ? EVAL_UNWIND_EXTENSION_FACTOR
320 : 1.0;
321 return _calculateCost((PConstraint) evaluation, input) * multiplier;
322 }
323
324 /**
325 * @since 1.7
326 */
327 protected double _calculateCost(final Inequality inequality, final IConstraintEvaluationContext input) {
328 return _calculateCost((PConstraint)inequality, input);
329 }
330
331 /**
332 * @since 1.7
333 */
334 protected double _calculateCost(final AggregatorConstraint aggregator, final IConstraintEvaluationContext input) {
335 return _calculateCost((PConstraint)aggregator, input);
336 }
337
338 /**
339 * @since 1.7
340 */
341 protected double _calculateCost(final NegativePatternCall call, final IConstraintEvaluationContext input) {
342 return _calculateCost((PConstraint)call, input);
343 }
344
345 /**
346 * @since 1.7
347 */
348 protected double _calculateCost(final PatternMatchCounter counter, final IConstraintEvaluationContext input) {
349 return _calculateCost((PConstraint)counter, input);
350 }
351
352 /**
353 * @since 1.7
354 */
355 protected double _calculateCost(final BinaryTransitiveClosure closure, final IConstraintEvaluationContext input) {
356 // if (input.getFreeVariables().size() == 1) return 3.0;
357 return StatisticsBasedConstraintCostFunction.DEFAULT_COST;
358 }
359
360 /**
361 * @since 2.0
362 */
363 protected double _calculateCost(final BinaryReflexiveTransitiveClosure closure, final IConstraintEvaluationContext input) {
364 // if (input.getFreeVariables().size() == 1) return 3.0;
365 return StatisticsBasedConstraintCostFunction.DEFAULT_COST;
366 }
367
368 /**
369 * Default cost calculation strategy
370 */
371 protected double _calculateCost(final PConstraint constraint, final IConstraintEvaluationContext input) {
372 if (input.getFreeVariables().isEmpty()) {
373 return 1.0;
374 } else {
375 return StatisticsBasedConstraintCostFunction.DEFAULT_COST;
376 }
377 }
378
379 /**
380 * @throws ViatraQueryRuntimeException
381 */
382 public double calculateCost(final PConstraint constraint, final IConstraintEvaluationContext input) {
383 Preconditions.checkArgument(constraint != null, "Set constraint value correctly");
384 if (constraint instanceof ExportedParameter) {
385 return _calculateCost((ExportedParameter) constraint, input);
386 } else if (constraint instanceof TypeFilterConstraint) {
387 return _calculateCost((TypeFilterConstraint) constraint, input);
388 } else if (constraint instanceof ConstantValue) {
389 return _calculateCost((ConstantValue) constraint, input);
390 } else if (constraint instanceof PositivePatternCall) {
391 return _calculateCost((PositivePatternCall) constraint, input);
392 } else if (constraint instanceof TypeConstraint) {
393 return _calculateCost((TypeConstraint) constraint, input);
394 } else if (constraint instanceof ExpressionEvaluation) {
395 return _calculateCost((ExpressionEvaluation) constraint, input);
396 } else if (constraint instanceof Inequality) {
397 return _calculateCost((Inequality) constraint, input);
398 } else if (constraint instanceof AggregatorConstraint) {
399 return _calculateCost((AggregatorConstraint) constraint, input);
400 } else if (constraint instanceof NegativePatternCall) {
401 return _calculateCost((NegativePatternCall) constraint, input);
402 } else if (constraint instanceof PatternMatchCounter) {
403 return _calculateCost((PatternMatchCounter) constraint, input);
404 } else if (constraint instanceof BinaryTransitiveClosure) {
405 return _calculateCost((BinaryTransitiveClosure) constraint, input);
406 } else if (constraint instanceof BinaryReflexiveTransitiveClosure) {
407 return _calculateCost((BinaryReflexiveTransitiveClosure) constraint, input);
408 } else {
409 // Default cost calculation
410 return _calculateCost(constraint, input);
411 }
412 }
413}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java
new file mode 100644
index 00000000..a517af25
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Balazs Grill, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.cost.impl;
10
11import java.util.Set;
12
13import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
14import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
17import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
18import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
19import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
20import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
21import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
22
23/**
24 * This class can be used to calculate cost of application of a constraint with a given adornment.
25 *
26 * For now the logic is based on the following principles:
27 *
28 * <li>The transitive closures, NACs and count finds are the most expensive operations
29 *
30 * <li>The number of free variables increase the cost
31 *
32 * <li>If all the variables of a constraint are free, then its cost equals to twice the number of its parameter
33 * variables. This solves the problem of unnecessary iteration over instances at the beginning of a plan (thus causing
34 * very long run times when executing the plan) by applying constraints based on structural features as soon as
35 * possible.
36 *
37 * <br>
38 *
39 * @author Marton Bur
40 * @since 1.4
41 *
42 */
43public class VariableBindingBasedCostFunction implements ICostFunction {
44
45 // Static cost definitions
46 private static int MAX = 1000;
47 private static int exportedParameterCost = MAX - 20;
48 private static int binaryTransitiveClosureCost = MAX - 50;
49 private static int nacCost = MAX - 100;
50 private static int aggregatorCost = MAX - 200;
51 private static int constantCost = 0;
52
53 @Override
54 public double apply(IConstraintEvaluationContext input) {
55 PConstraint constraint = input.getConstraint();
56 Set<PVariable> affectedVariables = constraint.getAffectedVariables();
57
58 int cost = 0;
59
60 // For constants the cost is determined to be 0.0
61 // The following constraints should be checks:
62 // * Binary transitive closure
63 // * NAC
64 // * count
65 // * exported parameter - only a metadata
66 if (constraint instanceof ConstantValue) {
67 cost = constantCost;
68 } else if (constraint instanceof BinaryTransitiveClosure) {
69 cost = binaryTransitiveClosureCost;
70 } else if (constraint instanceof NegativePatternCall) {
71 cost = nacCost;
72 } else if (constraint instanceof AggregatorConstraint) {
73 cost = aggregatorCost;
74 } else if (constraint instanceof ExportedParameter) {
75 cost = exportedParameterCost;
76 } else {
77 // In case of other constraints count the number of unbound variables
78 for (PVariable pVariable : affectedVariables) {
79 if (input.getFreeVariables().contains(pVariable)) {
80 // For each free variable ('without-value-variable') increase cost
81 cost += 1;
82 }
83 }
84 if (cost == affectedVariables.size()) {
85 // If all the variables are free, double the cost.
86 // This ensures that iteration costs more
87 cost *= 2;
88 }
89
90 }
91
92 return Float.valueOf(cost);
93 }
94
95}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java
new file mode 100644
index 00000000..9b4e9ea5
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java
@@ -0,0 +1,209 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Daniel Segesdi, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.util;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
20import tools.refinery.viatra.runtime.matchers.planning.operations.PApply;
21import tools.refinery.viatra.runtime.matchers.planning.operations.POperation;
22import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
23import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
24import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
25import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
26
27/**
28 *
29 * Helper methods for compiling SubPlans
30 *
31 * @author Marton Bur
32 *
33 */
34public class CompilerHelper {
35
36 private CompilerHelper() {/*Utility class constructor*/}
37
38 private static boolean isUserSpecified(PVariable var) {
39 return !var.isVirtual() && !var.getName().startsWith("_");
40 }
41
42 public static Map<PVariable, Integer> createVariableMapping(SubPlan plan) {
43 Map<PVariable, Integer> variableMapping = new HashMap<>();
44
45 int variableNumber = 0;
46
47 // Important note: this list might contain duplications when parameters are made equal inside the pattern
48 // This is the expected and normal behavior
49 List<PVariable> symbolicParameterVariables = plan.getBody().getSymbolicParameterVariables();
50 for (PVariable pVariable : symbolicParameterVariables) {
51 if (!variableMapping.containsKey(pVariable)) {
52 variableMapping.put(pVariable, variableNumber++);
53 }
54 }
55
56 List<PVariable> allVariables = new ArrayList<>(plan.getBody().getUniqueVariables());
57 Collections.sort(allVariables, (left, right) -> {
58 boolean leftUserSpecified = isUserSpecified(left);
59 boolean rightUserSpecified = isUserSpecified(right);
60 if (leftUserSpecified && !rightUserSpecified) {
61 return -1;
62 } else if (!leftUserSpecified && rightUserSpecified) {
63 return +1;
64 } else {
65 return left.getName().compareTo(right.getName());
66 }
67 });
68 for (PVariable pVariable : allVariables) {
69 if (!variableMapping.containsKey(pVariable)) {
70 variableMapping.put(pVariable, variableNumber++);
71 }
72 }
73
74 return variableMapping;
75 }
76
77 public static Map<PConstraint, Set<Integer>> cacheVariableBindings(SubPlan plan,
78 Map<PVariable, Integer> variableMappings, Set<PParameter> adornment) {
79
80 Set<Integer> externallyBoundVariables = getVariableIndicesForParameters(plan, variableMappings,
81 adornment);
82
83 Map<PConstraint, Set<Integer>> variableBindings = new HashMap<>();
84
85 List<SubPlan> allPlansInHierarchy = getAllParentPlans(plan);
86 for (SubPlan subPlan : allPlansInHierarchy) {
87 POperation operation = subPlan.getOperation();
88
89 if (operation instanceof PApply) {
90 PConstraint pConstraint = ((PApply) operation).getPConstraint();
91 Set<Integer> boundVariableIndices = getParametersBoundByParentPlan(variableMappings, subPlan);
92 boundVariableIndices.addAll(externallyBoundVariables);
93
94 variableBindings.put(pConstraint, boundVariableIndices);
95 }
96 }
97 return variableBindings;
98 }
99
100 /**
101 * Returns the list of variable indexes that are bound by the parent plan.
102 */
103 private static Set<Integer> getParametersBoundByParentPlan(Map<PVariable, Integer> variableMappings,
104 SubPlan subPlan) {
105 if (!subPlan.getParentPlans().isEmpty()) {
106 SubPlan parentPlan = subPlan.getParentPlans().get(0);
107 Set<PConstraint> enforcedConstraints = parentPlan.getAllEnforcedConstraints();
108 Set<PVariable> affectedVariables = getAffectedVariables(enforcedConstraints);
109 return getVariableIndices(variableMappings, affectedVariables);
110 }
111 return Collections.emptySet();
112 }
113
114 /**
115 * @param plan
116 * @return all the ancestor plans including the given plan
117 */
118 private static List<SubPlan> getAllParentPlans(SubPlan plan) {
119 SubPlan currentPlan = plan;
120 List<SubPlan> allPlans = new ArrayList<>();
121 allPlans.add(plan);
122 while (!currentPlan.getParentPlans().isEmpty()) {
123 // In the local search it is assumed that only a single parent exists
124 currentPlan = currentPlan.getParentPlans().get(0);
125 allPlans.add(currentPlan);
126 }
127
128 return allPlans;
129 }
130
131 /**
132 * @param variableMappings
133 * the mapping between variables and their indices
134 * @param variables
135 * variables to get the indices for
136 * @return the set of variable indices for the given variables
137 */
138 private static Set<Integer> getVariableIndices(Map<PVariable, Integer> variableMappings,
139 Iterable<PVariable> variables) {
140 Set<Integer> variableIndices = new HashSet<>();
141 for (PVariable pVariable : variables) {
142 variableIndices.add(variableMappings.get(pVariable));
143 }
144 return variableIndices;
145 }
146
147 /**
148 * Returns all affected variables of the given PConstraints.
149 */
150 private static Set<PVariable> getAffectedVariables(Set<PConstraint> pConstraints) {
151 Set<PVariable> allDeducedVariables = new HashSet<>();
152 for (PConstraint pConstraint : pConstraints) {
153 allDeducedVariables.addAll(pConstraint.getAffectedVariables());
154 }
155 return allDeducedVariables;
156 }
157
158 /**
159 * Transforms the index of a parameter into the index of a variable of the normalized body.
160 *
161 * @param plan
162 * the SubPlan containing the original body and its parameters
163 * @param variableMappings
164 * the mapping of PVariables to their indices
165 * @param parameters
166 * a set of parameters
167 * @return the index of the variable corresponding to the parameter at the given index
168 */
169 private static Set<Integer> getVariableIndicesForParameters(SubPlan plan,
170 Map<PVariable, Integer> variableMappings, Set<PParameter> parameters) {
171 Map<PParameter, PVariable> parameterMapping = new HashMap<>();
172 for (ExportedParameter constraint : plan.getBody().getSymbolicParameters()) {
173 parameterMapping.put(constraint.getPatternParameter(), constraint.getParameterVariable());
174 }
175
176 Set<Integer> variableIndices = new HashSet<>();
177 for (PParameter parameter : parameters) {
178 PVariable parameterVariable = parameterMapping.get(parameter);
179 if (parameterVariable == null) {
180 // XXX In case of older (pre-1.4) VIATRA versions, PParameters were not stable, see bug 498348
181 parameterVariable = plan.getBody().getVariableByNameChecked(parameter.getName());
182 }
183 Integer variableIndex = variableMappings.get(parameterVariable);
184 variableIndices.add(variableIndex);
185 }
186 return variableIndices;
187 }
188
189 /**
190 * Extracts the operations from a SubPlan into a list of POperations in the order of execution
191 *
192 * @param plan
193 * the SubPlan from wich the POperations should be extracted
194 * @return list of POperations extracted from the <code>plan</code>
195 */
196 public static List<POperation> createOperationsList(SubPlan plan) {
197 List<POperation> operationsList = new ArrayList<>();
198 while (plan.getParentPlans().size() > 0) {
199 operationsList.add(plan.getOperation());
200 SubPlan parentPlan = plan.getParentPlans().get(0);
201 plan = parentPlan;
202 }
203 operationsList.add(plan.getOperation());
204
205 Collections.reverse(operationsList);
206 return operationsList;
207 }
208
209}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java
new file mode 100644
index 00000000..58f4fc41
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Danil 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.planner.util;
10
11import java.util.Comparator;
12
13import tools.refinery.viatra.runtime.localsearch.planner.PConstraintInfo;
14
15/**
16 * @author Marton Bur
17 *
18 */
19public class OperationCostComparator implements Comparator<PConstraintInfo>{
20
21 @Override
22 public int compare(PConstraintInfo o1, PConstraintInfo o2) {
23 return Double.compare(o1.getCost(), o2.getCost());
24 }
25
26}
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java
new file mode 100644
index 00000000..8c50c694
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.profiler;
10
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.stream.Collectors;
15
16import tools.refinery.viatra.runtime.localsearch.MatchingFrame;
17import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter;
18import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher;
19import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
20import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation;
21import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
22import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanExecutor;
23
24/**
25 * This is a simple {@link ILocalSearchAdapter} which capable of counting
26 * each search operation execution then printing it in human readably form
27 * (along with the executed plans) using {@link #toString()}
28 * @author Grill Balázs
29 * @since 1.5
30 *
31 */
32public class LocalSearchProfilerAdapter implements ILocalSearchAdapter {
33
34 private final Map<MatcherReference, List<SearchPlan>> planReference = new HashMap<>();
35
36 private final Map<ISearchOperation, Integer> successfulOperationCounts = new HashMap<>();
37 private final Map<ISearchOperation, Integer> failedOperationCounts = new HashMap<>();
38
39 @Override
40 public void patternMatchingStarted(LocalSearchMatcher lsMatcher) {
41 MatcherReference key = new MatcherReference(lsMatcher.getPlanDescriptor().getQuery(),
42 lsMatcher.getPlanDescriptor().getAdornment());
43 planReference.put(key, lsMatcher.getPlan().stream().map(SearchPlanExecutor::getSearchPlan).collect(Collectors.toList()));
44 }
45
46 @Override
47 public void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isSuccessful) {
48 Map<ISearchOperation, Integer> counts = isSuccessful ? successfulOperationCounts : failedOperationCounts;
49 counts.merge(operation,
50 /*no previous entry*/1,
51 /*increase previous value*/(oldValue, v) -> oldValue + 1);
52 }
53
54 @Override
55 public String toString() {
56 StringBuilder sb = new StringBuilder();
57 for (java.util.Map.Entry<MatcherReference, List<SearchPlan>> entry: planReference.entrySet()){
58 sb.append(entry.getKey());
59 sb.append("\n");
60
61 sb.append(entry.getValue());
62
63 List<SearchPlan> bodies = entry.getValue();
64 sb.append("{\n");
65 for(int i=0;i<bodies.size();i++){
66 sb.append("\tbody #");sb.append(i);sb.append("(\n");
67 for(ISearchOperation operation : bodies.get(i).getOperations()){
68 final int successCount = successfulOperationCounts.computeIfAbsent(operation, op -> 0);
69 final int failCount = failedOperationCounts.computeIfAbsent(operation, op -> 0);
70 sb.append("\t\t");sb.append(successCount);
71 sb.append("\t");sb.append(failCount);
72 sb.append("\t");sb.append(successCount + failCount);
73 sb.append("\t");sb.append(operation);
74 sb.append("\n");
75 }
76 sb.append("\t)\n");
77 }
78 sb.append("}\n");
79 }
80 return sb.toString();
81 }
82
83}
diff --git a/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..c732af46
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
1Manifest-Version: 1.0
2Bundle-ManifestVersion: 2
3Bundle-Name: %pluginName
4Bundle-SymbolicName: viatra-runtime-rete-recipes;singleton:=true
5Automatic-Module-Name: viatra-runtime-rete-recipes
6Bundle-Version: 1.0.0.qualifier
7Bundle-ClassPath: viatra-runtime-rete-recipes.jar
8Bundle-Vendor: %providerName
9Bundle-Localization: plugin
10Bundle-RequiredExecutionEnvironment: JavaSE-1.7
11Export-Package: org.eclipse.viatra.query.runtime.rete.recipes,
12 org.eclipse.viatra.query.runtime.rete.recipes.impl,
13 org.eclipse.viatra.query.runtime.rete.recipes.util
14Require-Bundle: org.eclipse.core.runtime,
15 org.eclipse.emf.ecore;visibility:=reexport,
16 org.eclipse.xtext.xbase.lib,
17 org.eclipse.emf.ecore.xcore.lib
18Bundle-ActivationPolicy: lazy
diff --git a/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license
new file mode 100644
index 00000000..03d1d42b
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license
@@ -0,0 +1,4 @@
1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
3
4SPDX-License-Identifier: EPL-2.0
diff --git a/subprojects/viatra-runtime-rete-recipes/about.html b/subprojects/viatra-runtime-rete-recipes/about.html
new file mode 100644
index 00000000..d1d5593a
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/about.html
@@ -0,0 +1,26 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
2<html>
3<!--
4 Copyright (c) 2017, Eclipse.org Foundation, Inc.
5
6 SPDX-License-Identifier: LicenseRef-EPL-Steward
7-->
8<head>
9<title>About</title>
10<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
11</head>
12<body lang="EN-US">
13<h2>About This Content</h2>
14
15<p>March 18, 2019</p>
16<h3>License</h3>
17
18<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
19Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available at <a href="http://www.eclipse.org/org/documents/epl-v20.php">http://www.eclipse.org/legal/epl-v20.html</a>.
20For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
21
22<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
23apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
24indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
25</body>
26</html>
diff --git a/subprojects/viatra-runtime-rete-recipes/build.gradle.kts b/subprojects/viatra-runtime-rete-recipes/build.gradle.kts
new file mode 100644
index 00000000..b1b11b4e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/build.gradle.kts
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import tools.refinery.gradle.utils.SonarPropertiesUtils
8
9plugins {
10 id("tools.refinery.gradle.java-library")
11 id("tools.refinery.gradle.mwe2")
12 id("tools.refinery.gradle.sonarqube")
13}
14
15dependencies {
16 api(project(":refinery-viatra-runtime"))
17 api(libs.ecore)
18 mwe2(libs.ecore.codegen)
19 mwe2(libs.mwe.utils)
20 mwe2(libs.mwe2.lib)
21 mwe2(libs.slf4j.simple)
22 mwe2(libs.xtext.core)
23 mwe2(libs.xtext.xbase)
24}
25
26sourceSets {
27 main {
28 java.srcDir("src/main/emf-gen")
29 }
30}
31
32tasks {
33 val generateEPackage by registering(JavaExec::class) {
34 mainClass.set("org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher")
35 classpath(configurations.mwe2)
36 inputs.file("src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2")
37 inputs.file("src/main/resources/model/recipes.ecore")
38 inputs.file("src/main/resources/model/rete-recipes.genmodel")
39 outputs.file("build.properties")
40 outputs.file("META-INF/MANIFEST.MF")
41 outputs.file("plugin.xml")
42 outputs.file("plugin.properties")
43 outputs.dir("src/main/emf-gen")
44 args("src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2",
45 "-p", "rootPath=/$projectDir")
46 }
47
48 for (taskName in listOf("compileJava", "processResources", "generateEclipseSourceFolders")) {
49 named(taskName) {
50 dependsOn(generateEPackage)
51 }
52 }
53
54 clean {
55 delete("src/main/emf-gen")
56 }
57}
58
59sonarqube.properties {
60 SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/main/emf-gen/**")
61}
62
diff --git a/subprojects/viatra-runtime-rete-recipes/build.properties b/subprojects/viatra-runtime-rete-recipes/build.properties
new file mode 100644
index 00000000..97e4d6bd
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/build.properties
@@ -0,0 +1,15 @@
1# Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2# This program and the accompanying materials are made available under the
3# terms of the Eclipse Public License v. 2.0 which is available at
4# http://www.eclipse.org/legal/epl-v20.html.
5#
6# SPDX-License-Identifier: EPL-2.0
7
8bin.includes = viatra-runtime-rete-recipes.jar,\
9 model/,\
10 META-INF/,\
11 plugin.xml,\
12 plugin.properties
13jars.compile.order = viatra-runtime-rete-recipes.jar
14source.viatra-runtime-rete-recipes.jar = src-gen/
15output.viatra-runtime-rete-recipes.jar = bin/
diff --git a/subprojects/viatra-runtime-rete-recipes/plugin.properties b/subprojects/viatra-runtime-rete-recipes/plugin.properties
new file mode 100644
index 00000000..43a7236d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/plugin.properties
@@ -0,0 +1,9 @@
1# Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2# This program and the accompanying materials are made available under the
3# terms of the Eclipse Public License v. 2.0 which is available at
4# http://www.eclipse.org/legal/epl-v20.html.
5#
6# SPDX-License-Identifier: EPL-2.0
7
8pluginName = Rete-recipes Model
9providerName = www.example.org
diff --git a/subprojects/viatra-runtime-rete-recipes/plugin.xml b/subprojects/viatra-runtime-rete-recipes/plugin.xml
new file mode 100644
index 00000000..e4917f5d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/plugin.xml
@@ -0,0 +1,23 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<?eclipse version="3.0"?>
3
4<!--
5 Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
6 This program and the accompanying materials are made available under the
7 terms of the Eclipse Public License v. 2.0 which is available at
8 http://www.eclipse.org/legal/epl-v20.html.
9
10 SPDX-License-Identifier: EPL-2.0
11-->
12
13<plugin>
14
15 <extension point="org.eclipse.emf.ecore.generated_package">
16 <!-- @generated rete-recipes -->
17 <package
18 uri="https://refinery.tools/emf/2023/ViatraReteRecipes"
19 class="tools.refinery.viatra.runtime.rete.recipes.RecipesPackage"
20 genModel="src/main/resources/model/rete-recipes.genmodel"/>
21 </extension>
22
23</plugin>
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2 b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2
new file mode 100644
index 00000000..424cc9f5
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2
@@ -0,0 +1,25 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6module tools.refinery.viatra.runtime.rete.recipes.GenerateReteRecipes
7
8Workflow {
9 bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
10 projectMapping = {
11 projectName = "tools.refinery.refinery-viatra-runtime-rete-recipes"
12 path = "."
13 }
14 }
15
16 component = org.eclipse.emf.mwe.utils.DirectoryCleaner {
17 directory = "src/main/emf-gen"
18 }
19
20 component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
21 generateCustomClasses = false
22 genModel = "platform:/resource/tools.refinery.refinery-viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel"
23 srcPath = "platform:/resource/tools.refinery.refinery-viatra-runtime-rete-recipes/src/main/emf-gen"
24 }
25}
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java
new file mode 100644
index 00000000..2c252593
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java
@@ -0,0 +1,204 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.recipes.helper;
10
11import org.eclipse.emf.common.util.EList;
12import org.eclipse.emf.ecore.EAttribute;
13import org.eclipse.emf.ecore.EClass;
14import org.eclipse.emf.ecore.EObject;
15import org.eclipse.emf.ecore.EStructuralFeature;
16import org.eclipse.emf.ecore.util.EcoreUtil;
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.rete.recipes.RecipesPackage;
19import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
20
21import java.util.*;
22
23/**
24 * Stores a set of known <em>canonical</em> recipes, each representing a disjoint equivalence class of recipes, modulo
25 * {@link #isEquivalentRecipe(ReteNodeRecipe, ReteNodeRecipe)}.
26 *
27 * @author Gabor Bergmann
28 * @since 1.3
29 *
30 */
31public class RecipeRecognizer {
32 private static long nextRecipeEquivalenceClassID = 0;
33
34 /**
35 * if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here
36 */
37 Map<EClass, Set<ReteNodeRecipe>> canonicalRecipesByClass = new HashMap<>();
38 Map<Long, ReteNodeRecipe> canonicalRecipeByEquivalenceClassID = new HashMap<>();
39
40 private IQueryRuntimeContext runtimeContext;
41
42 /**
43 * @param can be null; if provided, further equivalences can be detected based on {@link IQueryRuntimeContext#wrapElement(Object)}
44 * @since 1.6
45 */
46 public RecipeRecognizer(IQueryRuntimeContext runtimeContext) {
47 this.runtimeContext = runtimeContext;
48 }
49 public RecipeRecognizer() {
50 this(null);
51 }
52
53 /**
54 * Recognizes when an equivalent canonical recipe is already known.
55 *
56 * @return an equivalent canonical recipe, or the null if no known equivalent found
57 */
58 public ReteNodeRecipe peekCanonicalRecipe(final ReteNodeRecipe recipe) {
59 // equivalence class already known
60 for (Long classID : recipe.getEquivalenceClassIDs()) {
61 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(classID);
62 if (knownRecipe != null)
63 return knownRecipe;
64 }
65
66 // equivalence class not known, but maybe equivalent recipe still
67 // available
68 Collection<ReteNodeRecipe> sameClassRecipes = getSameClassCanonicalRecipes(recipe);
69 for (ReteNodeRecipe knownRecipe : sameClassRecipes) {
70 if (isEquivalentRecipe(recipe, knownRecipe)) {
71 // FOUND EQUIVALENT RECIPE
72 recipe.getEquivalenceClassIDs().add(knownRecipe.getEquivalenceClassIDs().get(0));
73 return knownRecipe;
74 }
75 }
76
77 return null;
78 }
79
80 /**
81 * This recipe will be remembered as a canonical recipe. Method maintains both internal data structures and the
82 * equivalence class attribute of the recipe. PRECONDITION: {@link #peekCanonicalRecipe(ReteNodeRecipe)} must return
83 * null or the recipe itself
84 */
85 public void makeCanonical(final ReteNodeRecipe recipe) {
86 // this is a canonical recipe, chosen representative of its new
87 // equivalence class
88 if (recipe.getEquivalenceClassIDs().isEmpty()) {
89 recipe.getEquivalenceClassIDs().add(nextRecipeEquivalenceClassID++);
90 }
91 for (Long classID : recipe.getEquivalenceClassIDs()) {
92 canonicalRecipeByEquivalenceClassID.put(classID, recipe);
93 }
94 getSameClassCanonicalRecipes(recipe).add(recipe);
95 }
96
97 /**
98 * Ensures that there is an equivalent canonical recipe; if none is known yet, this recipe will be remembered as
99 * canonical.
100 *
101 * @return an equivalent canonical recipe; the argument recipe itself (which is made canonical) if no known
102 * equivalent found
103 */
104 public ReteNodeRecipe canonicalizeRecipe(final ReteNodeRecipe recipe) {
105 ReteNodeRecipe knownRecipe = peekCanonicalRecipe(recipe);
106 if (knownRecipe == null) {
107 knownRecipe = recipe;
108 makeCanonical(recipe);
109 }
110 return knownRecipe;
111 }
112
113 /**
114 * @return true iff recipe is a canonical recipe
115 */
116 public boolean isKnownCanonicalRecipe(final ReteNodeRecipe recipe) {
117 if (recipe.getEquivalenceClassIDs().isEmpty()) {
118 return false;
119 }
120 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(recipe.getEquivalenceClassIDs().get(0));
121 return recipe == knownRecipe;
122 }
123
124 private Set<ReteNodeRecipe> getSameClassCanonicalRecipes(final ReteNodeRecipe recipe) {
125 Set<ReteNodeRecipe> sameClassRecipes = canonicalRecipesByClass.get(recipe.eClass());
126 if (sameClassRecipes == null) {
127 sameClassRecipes = new HashSet<>();
128 canonicalRecipesByClass.put(recipe.eClass(), sameClassRecipes);
129 }
130 return sameClassRecipes;
131 }
132
133 private boolean isEquivalentRecipe(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) {
134 return new EqualityHelper(runtimeContext).equals(recipe, knownRecipe);
135 }
136
137 // TODO reuse in more cases later, e.g. switching join node parents, etc.
138 private static class EqualityHelper extends EcoreUtil.EqualityHelper {
139
140
141
142
143 private static final long serialVersionUID = -8841971394686015188L;
144
145 private static final EAttribute RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS =
146 RecipesPackage.eINSTANCE.getReteNodeRecipe_EquivalenceClassIDs();
147 private static final EAttribute CONSTANT_RECIPE_CONSTANT_VALUES =
148 RecipesPackage.eINSTANCE.getConstantRecipe_ConstantValues();
149 private static final EAttribute DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY =
150 RecipesPackage.eINSTANCE.getDiscriminatorBucketRecipe_BucketKey();
151
152 private IQueryRuntimeContext runtimeContext;
153
154 public EqualityHelper(IQueryRuntimeContext runtimeContext) {
155 this.runtimeContext = runtimeContext;
156 }
157
158 @Override
159 protected boolean haveEqualFeature(EObject eObject1, EObject eObject2, EStructuralFeature feature) {
160 // ignore differences in this attribute, as it may only be assigned
161 // after the equivalence check
162 if (RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS.equals(feature))
163 return true;
164
165 if (runtimeContext != null) {
166 // constant values
167 if (/*CONSTANT_RECIPE_CONSTANT_VALUES.equals(feature) ||*/ DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY.equals(feature)) {
168 // use runtime context to map to canonical wrapped form
169 // this is costly for constant recipes (TODO improve this), but essential for discriminator buckets
170
171 Object val1 = eObject1.eGet(feature);
172 Object val2 = eObject2.eGet(feature);
173
174 if (val1 != null && val2 != null) {
175 return runtimeContext.wrapElement(val1).equals(runtimeContext.wrapElement(val2));
176 } else {
177 return val1 == null && val2 == null;
178 }
179
180 }
181 }
182
183 // fallback to general comparison
184 return super.haveEqualFeature(eObject1, eObject2, feature);
185 }
186
187 @Override
188 public boolean equals(EObject eObject1, EObject eObject2) {
189 // short-circuit if already known to be equivalent
190 if (eObject1 instanceof ReteNodeRecipe) {
191 if (eObject2 instanceof ReteNodeRecipe) {
192 EList<Long> eqClassIDs1 = ((ReteNodeRecipe) eObject1).getEquivalenceClassIDs();
193 EList<Long> eqClassIDs2 = ((ReteNodeRecipe) eObject2).getEquivalenceClassIDs();
194
195 if (!Collections.disjoint(eqClassIDs1, eqClassIDs2))
196 return true;
197 }
198 }
199
200 // fallback to general comparison
201 return super.equals(eObject1, eObject2);
202 }
203 }
204}
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java
new file mode 100644
index 00000000..3070fcf5
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java
@@ -0,0 +1,81 @@
1/**
2 * Copyright (c) 2004-2014 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 */
9package tools.refinery.viatra.runtime.rete.recipes.helper;
10
11import org.eclipse.emf.common.util.EList;
12import tools.refinery.viatra.runtime.rete.recipes.*;
13
14import java.util.Collection;
15
16/**
17 * Static helper class for easy construction of recipes.
18 *
19 * @author Bergmann Gabor
20 */
21public class RecipesHelper {
22 private static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE;
23
24 private RecipesHelper() {/* Utility class constructor */}
25
26 /**
27 * @since 2.0
28 */
29 public static Mask mask(final int sourceArity, final Collection<Integer> sourceIndices) {
30 Mask mask = RecipesHelper.FACTORY.createMask();
31 mask.setSourceArity(sourceArity);
32 mask.getSourceIndices().addAll(sourceIndices);
33 return mask;
34 }
35
36 public static Mask mask(final int sourceArity, final int... sourceIndices) {
37 Mask mask = RecipesHelper.FACTORY.createMask();
38 mask.setSourceArity(sourceArity);
39 final EList<Integer> maskIndeces = mask.getSourceIndices();
40 for (int index : sourceIndices) {
41 maskIndeces.add(index);
42 }
43 return mask;
44 }
45
46 public static ProjectionIndexerRecipe projectionIndexerRecipe(final ReteNodeRecipe parent, final Mask mask) {
47 ProjectionIndexerRecipe recipe = RecipesHelper.FACTORY.createProjectionIndexerRecipe();
48 recipe.setParent(parent);
49 recipe.setMask(mask);
50 return recipe;
51 }
52
53 public static ExpressionDefinition expressionDefinition(final Object evaluator) {
54 ExpressionDefinition definition = RecipesHelper.FACTORY.createExpressionDefinition();
55 definition.setEvaluator(evaluator);
56 return definition;
57 }
58
59 public static InputRecipe inputRecipe(final Object inputKey, final String inputKeyID, final int arity) {
60 InputRecipe recipe = RecipesHelper.FACTORY.createInputRecipe();
61 recipe.setInputKey(inputKey);
62 recipe.setKeyArity(arity);
63 recipe.setKeyID(inputKeyID);
64 recipe.setTraceInfo(inputKeyID);
65 return recipe;
66 }
67
68 /**
69 * Mask can be null in case no tuple reordering or trimming is needed
70 */
71 public static InputFilterRecipe inputFilterRecipe(final ReteNodeRecipe parent, final Object inputKey,
72 final String inputKeyID, final Mask mask) {
73 InputFilterRecipe it = RecipesHelper.FACTORY.createInputFilterRecipe();
74 it.setParent(parent);
75 it.setInputKey(inputKey);
76 it.setKeyID(inputKeyID);
77 it.setTraceInfo(inputKeyID);
78 it.setMask(mask);
79 return it;
80 }
81}
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore
new file mode 100644
index 00000000..4eda701e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore
@@ -0,0 +1,400 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="recipes" nsURI="https://refinery.tools/emf/2023/ViatraReteRecipes"
4 nsPrefix="recipes">
5 <eClassifiers xsi:type="ecore:EClass" name="ReteRecipe">
6 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
7 <details key="documentation" value="Container for Rete recipes."/>
8 </eAnnotations>
9 <eStructuralFeatures xsi:type="ecore:EReference" name="recipeNodes" upperBound="-1"
10 eType="#//ReteNodeRecipe" containment="true" resolveProxies="false"/>
11 </eClassifiers>
12 <eClassifiers xsi:type="ecore:EClass" name="ReteNodeRecipe" abstract="true">
13 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
14 <details key="documentation" value="Abstract base class for model elements that represent &quot;Rete node recipes&quot;,&#xD;&#xA;that is DTOs that carry information for Rete network construction.&#xD;&#xA;&#xD;&#xA;@noimplement"/>
15 </eAnnotations>
16 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
17 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
18 <details key="documentation" value=" The width of tuples contained by this node."/>
19 <details key="body" value="throw new &lt;%java.lang.UnsupportedOperationException%>();"/>
20 </eAnnotations>
21 </eOperations>
22 <eStructuralFeatures xsi:type="ecore:EAttribute" name="traceInfo" unique="false"
23 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
24 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
25 <details key="documentation" value="Temporary construct for storing traceability information."/>
26 </eAnnotations>
27 </eStructuralFeatures>
28 <eStructuralFeatures xsi:type="ecore:EAttribute" name="equivalenceClassIDs" upperBound="-1"
29 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//ELong" transient="true">
30 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
31 <details key="documentation" value="If two recipes were found equivalent, a matching equivalence ID can be assigned to them by {@link RecipeRecognizer}. &#xD;&#xA;If two recipes share (at least one) equivalence ID, they are known to be equivalent.&#xD;&#xA;&#xD;&#xA;&lt;p>&#xD;&#xA;A difference in this attribute only does not preclude two recipe elements to be considered equal. &#xD;&#xA;If they are shown to be equivalent using deeper analysis, equivalence ids can be set so that the equivalence is recognized more easily the next time.&#xD;&#xA;&#xD;&#xA;@since 1.3"/>
32 </eAnnotations>
33 </eStructuralFeatures>
34 </eClassifiers>
35 <eClassifiers xsi:type="ecore:EClass" name="SingleParentNodeRecipe" abstract="true"
36 eSuperTypes="#//ReteNodeRecipe">
37 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
38 <details key="documentation" value="Abstract base class for single-parent node recipes."/>
39 </eAnnotations>
40 <eStructuralFeatures xsi:type="ecore:EReference" name="parent" eType="#//ReteNodeRecipe"/>
41 </eClassifiers>
42 <eClassifiers xsi:type="ecore:EClass" name="AlphaRecipe" abstract="true" eSuperTypes="#//SingleParentNodeRecipe">
43 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
44 <details key="documentation" value="Abstract base class for alpha node recipes."/>
45 </eAnnotations>
46 </eClassifiers>
47 <eClassifiers xsi:type="ecore:EClass" name="MultiParentNodeRecipe" abstract="true"
48 eSuperTypes="#//ReteNodeRecipe">
49 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
50 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
51 <details key="body" value="&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%>> _parents = this.getParents();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _get = _parents.get(0);&#xA;return _get.getArity();"/>
52 </eAnnotations>
53 </eOperations>
54 <eStructuralFeatures xsi:type="ecore:EReference" name="parents" upperBound="-1"
55 eType="#//ReteNodeRecipe"/>
56 </eClassifiers>
57 <eClassifiers xsi:type="ecore:EClass" name="MonotonicityInfo">
58 <eStructuralFeatures xsi:type="ecore:EReference" name="coreMask" eType="#//Mask"
59 containment="true" resolveProxies="false"/>
60 <eStructuralFeatures xsi:type="ecore:EReference" name="posetMask" eType="#//Mask"
61 containment="true" resolveProxies="false"/>
62 <eStructuralFeatures xsi:type="ecore:EAttribute" name="posetComparator" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"/>
63 </eClassifiers>
64 <eClassifiers xsi:type="ecore:EClass" name="UniquenessEnforcerRecipe" eSuperTypes="#//MultiParentNodeRecipe #//RederivableNodeRecipe">
65 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
66 <details key="documentation" value="Represents nodes that enforce tuple uniqueness, i.e. filter out&#xA;duplicate input tuples for output."/>
67 </eAnnotations>
68 </eClassifiers>
69 <eClassifiers xsi:type="ecore:EClass" name="ProductionRecipe" eSuperTypes="#//MultiParentNodeRecipe #//RederivableNodeRecipe">
70 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
71 <details key="documentation" value="The production node represents the output of the Rete network,&#xA;from which the results of a query can be read."/>
72 </eAnnotations>
73 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
74 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
75 <details key="body" value="return this.getMappedIndices().size();"/>
76 </eAnnotations>
77 </eOperations>
78 <eStructuralFeatures xsi:type="ecore:EReference" name="mappedIndices" upperBound="-1"
79 eType="#//StringIndexMapEntry" containment="true" resolveProxies="false">
80 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
81 <details key="documentation" value="String -> Index map.&#xA;Indicates the positions of parameters."/>
82 </eAnnotations>
83 </eStructuralFeatures>
84 <eStructuralFeatures xsi:type="ecore:EAttribute" name="pattern" unique="false"
85 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject">
86 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
87 <details key="documentation" value="Traceability link to defining pattern object (from EMFPatternLanguage)&#xA;TODO unused?"/>
88 </eAnnotations>
89 </eStructuralFeatures>
90 <eStructuralFeatures xsi:type="ecore:EAttribute" name="patternFQN" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
91 </eClassifiers>
92 <eClassifiers xsi:type="ecore:EClass" name="IndexerRecipe" abstract="true" eSuperTypes="#//SingleParentNodeRecipe">
93 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
94 <details key="documentation" value="Represents a node that indexes the contents of a parent based on a projection defined by a Mask."/>
95 </eAnnotations>
96 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
97 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
98 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = this.getMask();&#xA;return _mask.getSourceArity();"/>
99 </eAnnotations>
100 </eOperations>
101 <eStructuralFeatures xsi:type="ecore:EReference" name="mask" eType="#//Mask" containment="true"
102 resolveProxies="false"/>
103 </eClassifiers>
104 <eClassifiers xsi:type="ecore:EClass" name="ProjectionIndexerRecipe" eSuperTypes="#//IndexerRecipe">
105 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
106 <details key="documentation" value="Represents helper nodes that provide projection indexing for Beta nodes and user queries."/>
107 </eAnnotations>
108 </eClassifiers>
109 <eClassifiers xsi:type="ecore:EClass" name="AggregatorIndexerRecipe" eSuperTypes="#//IndexerRecipe">
110 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
111 <details key="documentation" value="Attached to an aggregator node, provides the aggregated values for outer join."/>
112 </eAnnotations>
113 </eClassifiers>
114 <eClassifiers xsi:type="ecore:EClass" name="BetaRecipe" abstract="true" eSuperTypes="#//ReteNodeRecipe">
115 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
116 <details key="documentation" value="Abstract base class for Beta node recipes."/>
117 </eAnnotations>
118 <eStructuralFeatures xsi:type="ecore:EReference" name="leftParent" eType="#//ProjectionIndexerRecipe"
119 containment="true" resolveProxies="false"/>
120 <eStructuralFeatures xsi:type="ecore:EReference" name="rightParent" eType="#//IndexerRecipe"
121 containment="true" resolveProxies="false">
122 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
123 <details key="documentation" value=" can be an AggregatorIndexer"/>
124 </eAnnotations>
125 </eStructuralFeatures>
126 </eClassifiers>
127 <eClassifiers xsi:type="ecore:EClass" name="Mask">
128 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
129 <details key="documentation" value="A mask defines the set of tuple variables that need to be taken into consideration for operations."/>
130 </eAnnotations>
131 <eStructuralFeatures xsi:type="ecore:EAttribute" name="sourceIndices" unique="false"
132 upperBound="-1" eType="#//Index">
133 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
134 <details key="documentation" value="The indices that are relevant for tuple operations."/>
135 </eAnnotations>
136 </eStructuralFeatures>
137 <eStructuralFeatures xsi:type="ecore:EAttribute" name="sourceArity" unique="false"
138 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
139 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
140 <details key="documentation" value="The arity of tuples."/>
141 </eAnnotations>
142 </eStructuralFeatures>
143 </eClassifiers>
144 <eClassifiers xsi:type="ecore:EDataType" name="Index" instanceClassName="java.lang.Integer">
145 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
146 <details key="documentation" value="Indexes tell which variable of tuples are relevant for a given operation.&#xA;TODO: is this necessary at all?"/>
147 </eAnnotations>
148 </eClassifiers>
149 <eClassifiers xsi:type="ecore:EClass" name="StringIndexMapEntry" instanceClassName="java.util.Map$Entry">
150 <eStructuralFeatures xsi:type="ecore:EAttribute" name="key" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
151 <eStructuralFeatures xsi:type="ecore:EAttribute" name="value" unique="false" eType="#//Index"/>
152 </eClassifiers>
153 <eClassifiers xsi:type="ecore:EClass" name="InputRecipe" eSuperTypes="#//ReteNodeRecipe">
154 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
155 <details key="documentation" value="Represents input nodes for the Rete network, i.e. nodes&#xA;that generate input tuples for processing."/>
156 </eAnnotations>
157 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
158 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
159 <details key="body" value="return getKeyArity();"/>
160 </eAnnotations>
161 </eOperations>
162 <eStructuralFeatures xsi:type="ecore:EAttribute" name="inputKey" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"
163 transient="true"/>
164 <eStructuralFeatures xsi:type="ecore:EAttribute" name="keyID" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
165 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
166 <details key="documentation" value="Temporary construct for identifying types over the wire.&#xA;TODO improve type references"/>
167 </eAnnotations>
168 </eStructuralFeatures>
169 <eStructuralFeatures xsi:type="ecore:EAttribute" name="keyArity" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
170 </eClassifiers>
171 <eClassifiers xsi:type="ecore:EClass" name="ConstantRecipe" eSuperTypes="#//ReteNodeRecipe">
172 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
173 <details key="documentation" value="Simple node that stores constant values."/>
174 </eAnnotations>
175 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
176 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
177 <details key="body" value="return this.getConstantValues().size();"/>
178 </eAnnotations>
179 </eOperations>
180 <eStructuralFeatures xsi:type="ecore:EAttribute" name="constantValues" unique="false"
181 upperBound="-1" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject">
182 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
183 <details key="documentation" value="Stores constant values. May be empty.&#xA;&#xA;TODO store constants as strings instead? (for easier serialization)"/>
184 </eAnnotations>
185 </eStructuralFeatures>
186 </eClassifiers>
187 <eClassifiers xsi:type="ecore:EClass" name="TransitiveClosureRecipe" eSuperTypes="#//AlphaRecipe">
188 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
189 <details key="documentation" value="Represents transitive closure."/>
190 </eAnnotations>
191 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
192 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
193 <details key="body" value="return 2;"/>
194 </eAnnotations>
195 </eOperations>
196 </eClassifiers>
197 <eClassifiers xsi:type="ecore:EClass" name="FilterRecipe" abstract="true" eSuperTypes="#//AlphaRecipe">
198 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
199 <details key="documentation" value="Abstract base class for nodes that implement filtering operations."/>
200 </eAnnotations>
201 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
202 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
203 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
204 </eAnnotations>
205 </eOperations>
206 </eClassifiers>
207 <eClassifiers xsi:type="ecore:EClass" name="InequalityFilterRecipe" eSuperTypes="#//FilterRecipe">
208 <eStructuralFeatures xsi:type="ecore:EAttribute" name="subject" unique="false"
209 eType="#//Index"/>
210 <eStructuralFeatures xsi:type="ecore:EAttribute" name="inequals" unique="false"
211 upperBound="-1" eType="#//Index"/>
212 </eClassifiers>
213 <eClassifiers xsi:type="ecore:EClass" name="EqualityFilterRecipe" eSuperTypes="#//FilterRecipe">
214 <eStructuralFeatures xsi:type="ecore:EAttribute" name="indices" unique="false"
215 upperBound="-1" eType="#//Index"/>
216 </eClassifiers>
217 <eClassifiers xsi:type="ecore:EClass" name="TransparentRecipe" eSuperTypes="#//FilterRecipe"/>
218 <eClassifiers xsi:type="ecore:EClass" name="TrimmerRecipe" eSuperTypes="#//AlphaRecipe">
219 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
220 <details key="documentation" value="Implements projection without uniqueness checking."/>
221 </eAnnotations>
222 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
223 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
224 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = this.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;return _sourceIndices.size();"/>
225 </eAnnotations>
226 </eOperations>
227 <eStructuralFeatures xsi:type="ecore:EReference" name="mask" eType="#//Mask" containment="true"
228 resolveProxies="false"/>
229 </eClassifiers>
230 <eClassifiers xsi:type="ecore:EClass" name="ExpressionDefinition">
231 <eStructuralFeatures xsi:type="ecore:EAttribute" name="evaluator" unique="false"
232 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"/>
233 </eClassifiers>
234 <eClassifiers xsi:type="ecore:EClass" name="ExpressionEnforcerRecipe" abstract="true"
235 eSuperTypes="#//AlphaRecipe">
236 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
237 <details key="documentation" value="type RuntimeExpressionEvaluator wraps tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator&#xA;class RuntimeExpressionDefinition extends ExpressionDefinition {&#xA;&#x9;RuntimeExpressionEvaluator evaluator&#xA;}"/>
238 </eAnnotations>
239 <eStructuralFeatures xsi:type="ecore:EReference" name="expression" eType="#//ExpressionDefinition"
240 containment="true" resolveProxies="false">
241 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
242 <details key="documentation" value="Provides traceability to expression representation."/>
243 </eAnnotations>
244 </eStructuralFeatures>
245 <eStructuralFeatures xsi:type="ecore:EReference" name="mappedIndices" upperBound="-1"
246 eType="#//StringIndexMapEntry" containment="true" resolveProxies="false">
247 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
248 <details key="documentation" value="String -> Index map.&#xA;Maps variable names in the expression to tuple indices."/>
249 </eAnnotations>
250 </eStructuralFeatures>
251 <eStructuralFeatures xsi:type="ecore:EAttribute" name="cacheOutput" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"/>
252 </eClassifiers>
253 <eClassifiers xsi:type="ecore:EClass" name="CheckRecipe" eSuperTypes="#//ExpressionEnforcerRecipe">
254 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
255 <details key="documentation" value="Computes the result of the boolean expression evaluation and&#xA;only passes tuples for which the result is true."/>
256 </eAnnotations>
257 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
258 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
259 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
260 </eAnnotations>
261 </eOperations>
262 </eClassifiers>
263 <eClassifiers xsi:type="ecore:EClass" name="EvalRecipe" eSuperTypes="#//ExpressionEnforcerRecipe">
264 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
265 <details key="documentation" value="Computes the result of expression evaluation and suffixes the result&#xA;to output tuples as the last element."/>
266 </eAnnotations>
267 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
268 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
269 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;int _arity = _parent.getArity();&#xA;return (1 + _arity);"/>
270 </eAnnotations>
271 </eOperations>
272 <eStructuralFeatures xsi:type="ecore:EAttribute" name="unwinding" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean">
273 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
274 <details key="documentation" value="@since 2.4"/>
275 </eAnnotations>
276 </eStructuralFeatures>
277 </eClassifiers>
278 <eClassifiers xsi:type="ecore:EClass" name="IndexerBasedAggregatorRecipe" abstract="true"
279 eSuperTypes="#//ReteNodeRecipe">
280 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
281 <details key="documentation" value="Represents a (compound) node that performs an aggregation operation.&#xA;Parent must be a ProjectionIndexer, which defines how tuples are to be aggregated.&#xA;Usable only through an Join with an AggregatorIndexer as the right parent"/>
282 </eAnnotations>
283 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
284 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
285 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _parent = this.getParent();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = _parent.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;int _size = _sourceIndices.size();&#xA;return (1 + _size);"/>
286 </eAnnotations>
287 </eOperations>
288 <eStructuralFeatures xsi:type="ecore:EReference" name="parent" eType="#//ProjectionIndexerRecipe"
289 containment="true" resolveProxies="false"/>
290 </eClassifiers>
291 <eClassifiers xsi:type="ecore:EClass" name="CountAggregatorRecipe" eSuperTypes="#//IndexerBasedAggregatorRecipe">
292 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
293 <details key="documentation" value="The count aggregator node represents a &quot;count find&quot; operation."/>
294 </eAnnotations>
295 </eClassifiers>
296 <eClassifiers xsi:type="ecore:EClass" name="JoinRecipe" eSuperTypes="#//BetaRecipe">
297 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
298 <details key="documentation" value="The most basic beta operation, the join node performs a join operation over two input tuple sets."/>
299 </eAnnotations>
300 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
301 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
302 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _leftParent = this.getLeftParent();&#xA;int _arity = _leftParent.getArity();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe%> _rightParent = this.getRightParent();&#xA;int _arity_1 = _rightParent.getArity();&#xA;int _plus = (_arity + _arity_1);&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe%> _rightParent_1 = this.getRightParent();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = _rightParent_1.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;int _size = _sourceIndices.size();&#xA;return (_plus - _size);"/>
303 </eAnnotations>
304 </eOperations>
305 <eStructuralFeatures xsi:type="ecore:EReference" name="rightParentComplementaryMask"
306 eType="#//Mask" containment="true" resolveProxies="false"/>
307 </eClassifiers>
308 <eClassifiers xsi:type="ecore:EClass" name="ExistenceJoinRecipe" abstract="true"
309 eSuperTypes="#//BetaRecipe">
310 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
311 <details key="documentation" value="Existence joins are TODO&#xA;&#xA;See http://en.wikipedia.org/wiki/Relational_algebra"/>
312 </eAnnotations>
313 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
314 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
315 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _leftParent = this.getLeftParent();&#xA;return _leftParent.getArity();"/>
316 </eAnnotations>
317 </eOperations>
318 </eClassifiers>
319 <eClassifiers xsi:type="ecore:EClass" name="SemiJoinRecipe" eSuperTypes="#//ExistenceJoinRecipe">
320 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
321 <details key="documentation" value="A semi-join is TODO&#xA;&#xA;See http://en.wikipedia.org/wiki/Relational_algebra"/>
322 </eAnnotations>
323 </eClassifiers>
324 <eClassifiers xsi:type="ecore:EClass" name="AntiJoinRecipe" eSuperTypes="#//ExistenceJoinRecipe">
325 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
326 <details key="documentation" value="An anti-join is TODO&#xA;&#xA;See http://en.wikipedia.org/wiki/Relational_algebra"/>
327 </eAnnotations>
328 </eClassifiers>
329 <eClassifiers xsi:type="ecore:EClass" name="InputFilterRecipe" eSuperTypes="#//FilterRecipe">
330 <eStructuralFeatures xsi:type="ecore:EAttribute" name="inputKey" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"
331 transient="true"/>
332 <eStructuralFeatures xsi:type="ecore:EAttribute" name="keyID" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString">
333 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
334 <details key="documentation" value="Temporary construct for identifying types over the wire.&#xA;TODO improve type references"/>
335 </eAnnotations>
336 </eStructuralFeatures>
337 <eStructuralFeatures xsi:type="ecore:EReference" name="mask" eType="#//Mask" containment="true"
338 resolveProxies="false"/>
339 </eClassifiers>
340 <eClassifiers xsi:type="ecore:EClass" name="SingleColumnAggregatorRecipe" eSuperTypes="#//AlphaRecipe #//RederivableNodeRecipe">
341 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
342 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
343 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.MonotonicityInfo%> info = getOptionalMonotonicityInfo();&#xA;if (info == null) {&#xA;&#x9;return 1 + getGroupByMask().getSourceIndices().size();&#xA;} else {&#x9;&#xA;&#x9;return info.getCoreMask().getSourceIndices().size() + info.getPosetMask().getSourceIndices().size();&#xA;}"/>
344 </eAnnotations>
345 </eOperations>
346 <eStructuralFeatures xsi:type="ecore:EAttribute" name="multisetAggregationOperator"
347 eType="#//AggregationOperator"/>
348 <eStructuralFeatures xsi:type="ecore:EAttribute" name="aggregableIndex" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
349 <eStructuralFeatures xsi:type="ecore:EReference" name="groupByMask" lowerBound="1"
350 eType="#//Mask" containment="true"/>
351 </eClassifiers>
352 <eClassifiers xsi:type="ecore:EDataType" name="AggregationOperator" instanceTypeName="tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator&lt;?, ?, ?>"/>
353 <eClassifiers xsi:type="ecore:EClass" name="DiscriminatorDispatcherRecipe" eSuperTypes="#//SingleParentNodeRecipe">
354 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
355 <details key="documentation" value="Node that sends tuples off to different buckets (attached as children) based on the value of a given column."/>
356 </eAnnotations>
357 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
358 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
359 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
360 </eAnnotations>
361 </eOperations>
362 <eStructuralFeatures xsi:type="ecore:EAttribute" name="discriminationColumnIndex"
363 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
364 </eClassifiers>
365 <eClassifiers xsi:type="ecore:EClass" name="DiscriminatorBucketRecipe" eSuperTypes="#//SingleParentNodeRecipe">
366 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
367 <details key="documentation" value="A bucket holds a filtered set of tuples of its parent DiscriminatorDispatcher; exactly those that have the given bucket key at their discrimination column."/>
368 </eAnnotations>
369 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
370 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
371 <details key="body" value="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
372 </eAnnotations>
373 </eOperations>
374 <eStructuralFeatures xsi:type="ecore:EAttribute" name="bucketKey" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EJavaObject"/>
375 </eClassifiers>
376 <eClassifiers xsi:type="ecore:EClass" name="RederivableNodeRecipe" abstract="true">
377 <eStructuralFeatures xsi:type="ecore:EAttribute" name="deleteRederiveEvaluation"
378 eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean" defaultValueLiteral="false"/>
379 <eStructuralFeatures xsi:type="ecore:EReference" name="optionalMonotonicityInfo"
380 eType="#//MonotonicityInfo" containment="true"/>
381 </eClassifiers>
382 <eClassifiers xsi:type="ecore:EClass" name="RelationEvaluationRecipe" eSuperTypes="#//MultiParentNodeRecipe">
383 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
384 <details key="documentation" value="@since 2.8"/>
385 </eAnnotations>
386 <eStructuralFeatures xsi:type="ecore:EReference" name="evaluator" eType="#//ExpressionDefinition"/>
387 </eClassifiers>
388 <eClassifiers xsi:type="ecore:EClass" name="RepresentativeElectionRecipe" eSuperTypes="#//AlphaRecipe">
389 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
390 <details key="documentation" value="Represents represenative election."/>
391 </eAnnotations>
392 <eOperations name="getArity" unique="false" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt">
393 <eAnnotations source="http://www.eclipse.org/emf/2002/GenModel">
394 <details key="body" value="return 2;"/>
395 </eAnnotations>
396 </eOperations>
397 <eStructuralFeatures xsi:type="ecore:EAttribute" name="connectivity" eType="#//Connectivity"/>
398 </eClassifiers>
399 <eClassifiers xsi:type="ecore:EDataType" name="Connectivity" instanceClassName="tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.Connectivity"/>
400</ecore:EPackage>
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license
new file mode 100644
index 00000000..03d1d42b
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license
@@ -0,0 +1,4 @@
1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
3
4SPDX-License-Identifier: EPL-2.0
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel
new file mode 100644
index 00000000..6b44fde6
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel
@@ -0,0 +1,171 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
3 xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" copyrightText="Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro&#xA;Copyright (c) 2023 The Refinery Authors &lt;https://refinery.tools>&#xA;This program and the accompanying materials are made available under the&#xA;terms of the Eclipse Public License v. 2.0 which is available at&#xA;http://www.eclipse.org/legal/epl-v20.html.&#xA;&#xA;SPDX-License-Identifier: EPL-2.0"
4 modelDirectory="/tools.refinery.refinery-viatra-runtime-rete-recipes/src/main/emf-gen"
5 modelPluginID="viatra-runtime-rete-recipes" runtimeJar="true" forceOverwrite="true"
6 modelName="Rete-recipes" updateClasspath="false" nonNLSMarkers="true" rootExtendsClass="org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container"
7 testsDirectory="" importerID="org.eclipse.emf.importer.ecore" containmentProxies="true"
8 complianceLevel="7.0" language="en" operationReflection="true">
9 <genAnnotations source="http://www.eclipse.org/emf/2002/GenModel/exporter/org.eclipse.xsd.ecore.exporter">
10 <genAnnotations source="selectedPackages">
11 <details key="http://www.eclipse.org/emf/2002/Ecore" value="Ecore.xsd"/>
12 </genAnnotations>
13 <details key="directoryURI" value="."/>
14 </genAnnotations>
15 <genAnnotations source="http://www.eclipse.org/emf/2002/GenModel/exporter/org.eclipse.xsd.ecore.exporter.xmi">
16 <genAnnotations source="selectedPackages">
17 <details key="http://www.eclipse.org/emf/2002/Ecore" value="EcoreXMI.xsd"/>
18 </genAnnotations>
19 <details key="directoryURI" value="."/>
20 </genAnnotations>
21 <foreignModel>recipes.ecore</foreignModel>
22 <modelPluginVariables>org.eclipse.xtext.xbase.lib</modelPluginVariables>
23 <modelPluginVariables>org.eclipse.emf.ecore.xcore.lib</modelPluginVariables>
24 <genPackages prefix="Recipes" basePackage="tools.refinery.viatra.runtime.rete" disposableProviderFactory="true"
25 ecorePackage="recipes.ecore#/">
26 <genDataTypes ecoreDataType="recipes.ecore#//Index"/>
27 <genDataTypes ecoreDataType="recipes.ecore#//AggregationOperator"/>
28 <genDataTypes ecoreDataType="recipes.ecore#//Connectivity"/>
29 <genClasses ecoreClass="recipes.ecore#//ReteRecipe">
30 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//ReteRecipe/recipeNodes"/>
31 </genClasses>
32 <genClasses image="false" ecoreClass="recipes.ecore#//ReteNodeRecipe">
33 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/traceInfo"/>
34 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/equivalenceClassIDs"/>
35 <genOperations ecoreOperation="recipes.ecore#//ReteNodeRecipe/getArity" body="throw new &lt;%java.lang.UnsupportedOperationException%>();"/>
36 </genClasses>
37 <genClasses image="false" ecoreClass="recipes.ecore#//SingleParentNodeRecipe">
38 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference recipes.ecore#//SingleParentNodeRecipe/parent"/>
39 </genClasses>
40 <genClasses image="false" ecoreClass="recipes.ecore#//AlphaRecipe"/>
41 <genClasses image="false" ecoreClass="recipes.ecore#//MultiParentNodeRecipe">
42 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference recipes.ecore#//MultiParentNodeRecipe/parents"/>
43 <genOperations ecoreOperation="recipes.ecore#//MultiParentNodeRecipe/getArity"
44 body="&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%>> _parents = this.getParents();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _get = _parents.get(0);&#xA;return _get.getArity();"/>
45 </genClasses>
46 <genClasses ecoreClass="recipes.ecore#//MonotonicityInfo">
47 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//MonotonicityInfo/coreMask"/>
48 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//MonotonicityInfo/posetMask"/>
49 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//MonotonicityInfo/posetComparator"/>
50 </genClasses>
51 <genClasses ecoreClass="recipes.ecore#//UniquenessEnforcerRecipe"/>
52 <genClasses ecoreClass="recipes.ecore#//ProductionRecipe">
53 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//ProductionRecipe/mappedIndices"/>
54 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ProductionRecipe/pattern"/>
55 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ProductionRecipe/patternFQN"/>
56 <genOperations ecoreOperation="recipes.ecore#//ProductionRecipe/getArity" body="return this.getMappedIndices().size();"/>
57 </genClasses>
58 <genClasses image="false" ecoreClass="recipes.ecore#//IndexerRecipe">
59 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//IndexerRecipe/mask"/>
60 <genOperations ecoreOperation="recipes.ecore#//IndexerRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = this.getMask();&#xA;return _mask.getSourceArity();"/>
61 </genClasses>
62 <genClasses ecoreClass="recipes.ecore#//ProjectionIndexerRecipe"/>
63 <genClasses ecoreClass="recipes.ecore#//AggregatorIndexerRecipe"/>
64 <genClasses image="false" ecoreClass="recipes.ecore#//BetaRecipe">
65 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//BetaRecipe/leftParent"/>
66 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//BetaRecipe/rightParent"/>
67 </genClasses>
68 <genClasses ecoreClass="recipes.ecore#//Mask">
69 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//Mask/sourceIndices"/>
70 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//Mask/sourceArity"/>
71 </genClasses>
72 <genClasses ecoreClass="recipes.ecore#//StringIndexMapEntry">
73 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//StringIndexMapEntry/key"/>
74 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//StringIndexMapEntry/value"/>
75 </genClasses>
76 <genClasses ecoreClass="recipes.ecore#//InputRecipe">
77 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InputRecipe/inputKey"/>
78 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InputRecipe/keyID"/>
79 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InputRecipe/keyArity"/>
80 <genOperations ecoreOperation="recipes.ecore#//InputRecipe/getArity" body="return getKeyArity();"/>
81 </genClasses>
82 <genClasses ecoreClass="recipes.ecore#//ConstantRecipe">
83 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ConstantRecipe/constantValues"/>
84 <genOperations ecoreOperation="recipes.ecore#//ConstantRecipe/getArity" body="return this.getConstantValues().size();"/>
85 </genClasses>
86 <genClasses ecoreClass="recipes.ecore#//TransitiveClosureRecipe">
87 <genOperations ecoreOperation="recipes.ecore#//TransitiveClosureRecipe/getArity"
88 body="return 2;"/>
89 </genClasses>
90 <genClasses image="false" ecoreClass="recipes.ecore#//FilterRecipe">
91 <genOperations ecoreOperation="recipes.ecore#//FilterRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
92 </genClasses>
93 <genClasses ecoreClass="recipes.ecore#//InequalityFilterRecipe">
94 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InequalityFilterRecipe/subject"/>
95 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InequalityFilterRecipe/inequals"/>
96 </genClasses>
97 <genClasses ecoreClass="recipes.ecore#//EqualityFilterRecipe">
98 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//EqualityFilterRecipe/indices"/>
99 </genClasses>
100 <genClasses ecoreClass="recipes.ecore#//TransparentRecipe"/>
101 <genClasses ecoreClass="recipes.ecore#//TrimmerRecipe">
102 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//TrimmerRecipe/mask"/>
103 <genOperations ecoreOperation="recipes.ecore#//TrimmerRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = this.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;return _sourceIndices.size();"/>
104 </genClasses>
105 <genClasses ecoreClass="recipes.ecore#//ExpressionDefinition">
106 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ExpressionDefinition/evaluator"/>
107 </genClasses>
108 <genClasses image="false" ecoreClass="recipes.ecore#//ExpressionEnforcerRecipe">
109 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//ExpressionEnforcerRecipe/expression"/>
110 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//ExpressionEnforcerRecipe/mappedIndices"/>
111 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ExpressionEnforcerRecipe/cacheOutput"/>
112 </genClasses>
113 <genClasses ecoreClass="recipes.ecore#//CheckRecipe">
114 <genOperations ecoreOperation="recipes.ecore#//CheckRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
115 </genClasses>
116 <genClasses ecoreClass="recipes.ecore#//EvalRecipe">
117 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//EvalRecipe/unwinding"/>
118 <genOperations ecoreOperation="recipes.ecore#//EvalRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;int _arity = _parent.getArity();&#xA;return (1 + _arity);"/>
119 </genClasses>
120 <genClasses image="false" ecoreClass="recipes.ecore#//IndexerBasedAggregatorRecipe">
121 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//IndexerBasedAggregatorRecipe/parent"/>
122 <genOperations ecoreOperation="recipes.ecore#//IndexerBasedAggregatorRecipe/getArity"
123 body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _parent = this.getParent();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = _parent.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;int _size = _sourceIndices.size();&#xA;return (1 + _size);"/>
124 </genClasses>
125 <genClasses ecoreClass="recipes.ecore#//CountAggregatorRecipe"/>
126 <genClasses ecoreClass="recipes.ecore#//JoinRecipe">
127 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//JoinRecipe/rightParentComplementaryMask"/>
128 <genOperations ecoreOperation="recipes.ecore#//JoinRecipe/getArity" body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _leftParent = this.getLeftParent();&#xA;int _arity = _leftParent.getArity();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe%> _rightParent = this.getRightParent();&#xA;int _arity_1 = _rightParent.getArity();&#xA;int _plus = (_arity + _arity_1);&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe%> _rightParent_1 = this.getRightParent();&#xA;&lt;%tools.refinery.viatra.runtime.rete.recipes.Mask%> _mask = _rightParent_1.getMask();&#xA;&lt;%org.eclipse.emf.common.util.EList%>&lt;&lt;%java.lang.Integer%>> _sourceIndices = _mask.getSourceIndices();&#xA;int _size = _sourceIndices.size();&#xA;return (_plus - _size);"/>
129 </genClasses>
130 <genClasses image="false" ecoreClass="recipes.ecore#//ExistenceJoinRecipe">
131 <genOperations ecoreOperation="recipes.ecore#//ExistenceJoinRecipe/getArity"
132 body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe%> _leftParent = this.getLeftParent();&#xA;return _leftParent.getArity();"/>
133 </genClasses>
134 <genClasses ecoreClass="recipes.ecore#//SemiJoinRecipe"/>
135 <genClasses ecoreClass="recipes.ecore#//AntiJoinRecipe"/>
136 <genClasses ecoreClass="recipes.ecore#//InputFilterRecipe">
137 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InputFilterRecipe/inputKey"/>
138 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//InputFilterRecipe/keyID"/>
139 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//InputFilterRecipe/mask"/>
140 </genClasses>
141 <genClasses ecoreClass="recipes.ecore#//SingleColumnAggregatorRecipe">
142 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//SingleColumnAggregatorRecipe/multisetAggregationOperator"/>
143 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//SingleColumnAggregatorRecipe/aggregableIndex"/>
144 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//SingleColumnAggregatorRecipe/groupByMask"/>
145 <genOperations ecoreOperation="recipes.ecore#//SingleColumnAggregatorRecipe/getArity"
146 body="&lt;%tools.refinery.viatra.runtime.rete.recipes.MonotonicityInfo%> info = getOptionalMonotonicityInfo();&#xA;if (info == null) {&#xA;&#x9;return 1 + getGroupByMask().getSourceIndices().size();&#xA;} else {&#x9;&#xA;&#x9;return info.getCoreMask().getSourceIndices().size() + info.getPosetMask().getSourceIndices().size();&#xA;}"/>
147 </genClasses>
148 <genClasses ecoreClass="recipes.ecore#//DiscriminatorDispatcherRecipe">
149 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//DiscriminatorDispatcherRecipe/discriminationColumnIndex"/>
150 <genOperations ecoreOperation="recipes.ecore#//DiscriminatorDispatcherRecipe/getArity"
151 body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
152 </genClasses>
153 <genClasses ecoreClass="recipes.ecore#//DiscriminatorBucketRecipe">
154 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//DiscriminatorBucketRecipe/bucketKey"/>
155 <genOperations ecoreOperation="recipes.ecore#//DiscriminatorBucketRecipe/getArity"
156 body="&lt;%tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe%> _parent = this.getParent();&#xA;return _parent.getArity();"/>
157 </genClasses>
158 <genClasses image="false" ecoreClass="recipes.ecore#//RederivableNodeRecipe">
159 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//RederivableNodeRecipe/deleteRederiveEvaluation"/>
160 <genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference recipes.ecore#//RederivableNodeRecipe/optionalMonotonicityInfo"/>
161 </genClasses>
162 <genClasses ecoreClass="recipes.ecore#//RelationEvaluationRecipe">
163 <genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference recipes.ecore#//RelationEvaluationRecipe/evaluator"/>
164 </genClasses>
165 <genClasses ecoreClass="recipes.ecore#//RepresentativeElectionRecipe">
166 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//RepresentativeElectionRecipe/connectivity"/>
167 <genOperations ecoreOperation="recipes.ecore#//RepresentativeElectionRecipe/getArity"
168 body="return 2;"/>
169 </genClasses>
170 </genPackages>
171</genmodel:GenModel>
diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license
new file mode 100644
index 00000000..03d1d42b
--- /dev/null
+++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license
@@ -0,0 +1,4 @@
1Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro
2Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
3
4SPDX-License-Identifier: EPL-2.0
diff --git a/subprojects/viatra-runtime-rete/about.html b/subprojects/viatra-runtime-rete/about.html
new file mode 100644
index 00000000..d1d5593a
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/about.html
@@ -0,0 +1,26 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
2<html>
3<!--
4 Copyright (c) 2017, Eclipse.org Foundation, Inc.
5
6 SPDX-License-Identifier: LicenseRef-EPL-Steward
7-->
8<head>
9<title>About</title>
10<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
11</head>
12<body lang="EN-US">
13<h2>About This Content</h2>
14
15<p>March 18, 2019</p>
16<h3>License</h3>
17
18<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
19Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available at <a href="http://www.eclipse.org/org/documents/epl-v20.php">http://www.eclipse.org/legal/epl-v20.html</a>.
20For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
21
22<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
23apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
24indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
25</body>
26</html>
diff --git a/subprojects/viatra-runtime-rete/build.gradle.kts b/subprojects/viatra-runtime-rete/build.gradle.kts
new file mode 100644
index 00000000..7e795a90
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/build.gradle.kts
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 implementation(project(":refinery-viatra-runtime"))
13 implementation(project(":refinery-viatra-runtime-rete-recipes"))
14 implementation(libs.slf4j.log4j)
15}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java
new file mode 100644
index 00000000..2588bde1
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java
@@ -0,0 +1,474 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Objects;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
22import tools.refinery.viatra.runtime.matchers.util.Clearable;
23import tools.refinery.viatra.runtime.matchers.util.Direction;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
25import tools.refinery.viatra.runtime.rete.index.Indexer;
26import tools.refinery.viatra.runtime.rete.index.StandardIndexer;
27import tools.refinery.viatra.runtime.rete.network.Node;
28import tools.refinery.viatra.runtime.rete.network.Receiver;
29import tools.refinery.viatra.runtime.rete.network.ReteContainer;
30import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
31import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
32import tools.refinery.viatra.runtime.rete.single.SingleInputNode;
33
34/**
35 * Groups incoming tuples by the given mask, and aggregates values at a specific index in each group.
36 * <p>
37 * Direct children are not supported, use via outer join indexers instead.
38 * <p>
39 * There are both timeless and timely implementations.
40 *
41 * @author Tamas Szabo
42 * @since 2.2
43 *
44 */
45public abstract class AbstractColumnAggregatorNode<Domain, Accumulator, AggregateResult> extends SingleInputNode
46 implements Clearable, IAggregatorNode {
47
48 /**
49 * @since 1.6
50 */
51 protected final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator;
52
53 /**
54 * @since 1.6
55 */
56 protected final TupleMask groupMask;
57
58 /**
59 * @since 1.6
60 */
61 protected final TupleMask columnMask;
62
63 /**
64 * @since 1.6
65 */
66 protected final int sourceWidth;
67
68 /**
69 * @since 1.6
70 */
71 protected final IQueryRuntimeContext runtimeContext;
72
73 protected final AggregateResult NEUTRAL;
74
75 protected AggregatorOuterIndexer aggregatorOuterIndexer;
76
77 @SuppressWarnings("rawtypes")
78 protected AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers;
79
80 /**
81 * Creates a new column aggregator node.
82 *
83 * @param reteContainer
84 * the RETE container of the node
85 * @param operator
86 * the aggregation operator
87 * @param deleteRederiveEvaluation
88 * true if the node should run in DRED mode, false otherwise
89 * @param groupMask
90 * the mask that masks a tuple to obtain the key that we are grouping-by
91 * @param columnMask
92 * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over
93 * @param posetComparator
94 * the poset comparator for the column, if known, otherwise it can be null
95 * @since 1.6
96 */
97 public AbstractColumnAggregatorNode(final ReteContainer reteContainer,
98 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
99 final TupleMask groupMask, final TupleMask columnMask) {
100 super(reteContainer);
101 this.operator = operator;
102 this.groupMask = groupMask;
103 this.columnMask = columnMask;
104 this.sourceWidth = groupMask.indices.length;
105 this.runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext();
106 this.NEUTRAL = operator.getAggregate(operator.createNeutral());
107 reteContainer.registerClearable(this);
108 }
109
110 /**
111 * Creates a new column aggregator node.
112 *
113 * @param reteContainer
114 * the RETE container of the node
115 * @param operator
116 * the aggregation operator
117 * @param groupMask
118 * the mask that masks a tuple to obtain the key that we are grouping-by
119 * @param aggregatedColumn
120 * the index of the column that the aggregator node is aggregating over
121 */
122 public AbstractColumnAggregatorNode(final ReteContainer reteContainer,
123 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
124 final TupleMask groupMask, final int aggregatedColumn) {
125 this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth));
126 }
127
128 @Override
129 public CommunicationTracker getCommunicationTracker() {
130 return this.reteContainer.getCommunicationTracker();
131 }
132
133 @Override
134 public void pullInto(Collection<Tuple> collector, boolean flush) {
135 // DIRECT CHILDREN NOT SUPPORTED
136 throw new UnsupportedOperationException();
137 }
138
139 @Override
140 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
141 // DIRECT CHILDREN NOT SUPPORTED
142 throw new UnsupportedOperationException();
143 }
144
145 @Override
146 public void appendChild(Receiver receiver) {
147 // DIRECT CHILDREN NOT SUPPORTED
148 throw new UnsupportedOperationException();
149 }
150
151 @Override
152 public Indexer getAggregatorOuterIndexer() {
153 if (aggregatorOuterIndexer == null) {
154 aggregatorOuterIndexer = new AggregatorOuterIndexer();
155 this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer);
156 }
157 return aggregatorOuterIndexer;
158 }
159
160 @Override
161 public Indexer getAggregatorOuterIdentityIndexer(final int resultPositionInSignature) {
162 if (aggregatorOuterIdentityIndexers == null) {
163 aggregatorOuterIdentityIndexers = new AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[sourceWidth
164 + 1];
165 }
166 if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) {
167 aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer(
168 resultPositionInSignature);
169 this.getCommunicationTracker().registerDependency(this,
170 aggregatorOuterIdentityIndexers[resultPositionInSignature]);
171 }
172 return aggregatorOuterIdentityIndexers[resultPositionInSignature];
173 }
174
175 /**
176 * @since 2.4
177 */
178 public void propagateAggregateResultUpdate(final Tuple group, final AggregateResult oldValue,
179 final AggregateResult newValue, final Timestamp timestamp) {
180 if (!Objects.equals(oldValue, newValue)) {
181 propagate(Direction.DELETE, group, oldValue, timestamp);
182 propagate(Direction.INSERT, group, newValue, timestamp);
183 }
184 }
185
186 /**
187 * @since 2.4
188 */
189 @SuppressWarnings("unchecked")
190 public void propagate(final Direction direction, final Tuple group, final AggregateResult value,
191 final Timestamp timestamp) {
192 final Tuple tuple = tupleFromAggregateResult(group, value);
193
194 if (aggregatorOuterIndexer != null) {
195 aggregatorOuterIndexer.propagate(direction, tuple, group, timestamp);
196 }
197 if (aggregatorOuterIdentityIndexers != null) {
198 for (final AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers) {
199 if (aggregatorOuterIdentityIndexer != null) {
200 aggregatorOuterIdentityIndexer.propagate(direction, tuple, group, timestamp);
201 }
202 }
203 }
204 }
205
206 public abstract Tuple getAggregateTuple(final Tuple key);
207
208 /**
209 * @since 2.4
210 */
211 public abstract Map<Tuple, Timeline<Timestamp>> getAggregateTupleTimeline(final Tuple key);
212
213 public abstract AggregateResult getAggregateResult(final Tuple key);
214
215 /**
216 * @since 2.4
217 */
218 public abstract Map<AggregateResult, Timeline<Timestamp>> getAggregateResultTimeline(final Tuple key);
219
220 protected Tuple tupleFromAggregateResult(final Tuple groupTuple, final AggregateResult aggregateResult) {
221 if (aggregateResult == null) {
222 return null;
223 } else {
224 return Tuples.staticArityLeftInheritanceTupleOf(groupTuple, runtimeContext.wrapElement(aggregateResult));
225 }
226 }
227
228 /**
229 * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original
230 * signature.
231 *
232 * @author Gabor Bergmann
233 * @author Tamas Szabo
234 *
235 */
236 protected class AggregatorOuterIndexer extends StandardIndexer {
237
238 /**
239 * @since 2.4
240 */
241 protected NetworkStructureChangeSensitiveLogic logic;
242
243 public AggregatorOuterIndexer() {
244 super(AbstractColumnAggregatorNode.this.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1));
245 this.parent = AbstractColumnAggregatorNode.this;
246 this.logic = createLogic();
247 }
248
249 @Override
250 public void networkStructureChanged() {
251 super.networkStructureChanged();
252 this.logic = createLogic();
253 }
254
255 @Override
256 public Collection<Tuple> get(final Tuple signature) {
257 return this.logic.get(signature);
258 }
259
260 @Override
261 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
262 return this.logic.getTimeline(signature);
263 }
264
265 /**
266 * @since 2.4
267 */
268 public void propagate(final Direction direction, final Tuple tuple, final Tuple group,
269 final Timestamp timestamp) {
270 if (tuple != null) {
271 propagate(direction, tuple, group, true, timestamp);
272 }
273 }
274
275 @Override
276 public Node getActiveNode() {
277 return AbstractColumnAggregatorNode.this;
278 }
279
280 /**
281 * @since 2.4
282 */
283 protected NetworkStructureChangeSensitiveLogic createLogic() {
284 if (this.reteContainer.isTimelyEvaluation()
285 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
286 return this.TIMELY;
287 } else {
288 return this.TIMELESS;
289 }
290 }
291
292 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
293
294 @Override
295 public Collection<Tuple> get(final Tuple signature) {
296 final Tuple aggregateTuple = getAggregateTuple(signature);
297 if (aggregateTuple == null) {
298 return null;
299 } else {
300 return Collections.singleton(aggregateTuple);
301 }
302 }
303
304 @Override
305 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
306 throw new UnsupportedOperationException();
307 }
308
309 };
310
311 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
312
313 @Override
314 public Collection<Tuple> get(final Tuple signatureWithResult) {
315 return TIMELESS.get(signatureWithResult);
316 }
317
318 @Override
319 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
320 final Map<Tuple, Timeline<Timestamp>> aggregateTuples = getAggregateTupleTimeline(signature);
321 if (aggregateTuples.isEmpty()) {
322 return null;
323 } else {
324 return aggregateTuples;
325 }
326 }
327
328 };
329
330 }
331
332 /**
333 * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for
334 * this index is the original 'group by' masked tuple, with the suspected result inserted at position
335 * resultPositionInSignature.
336 *
337 * @author Gabor Bergmann
338 * @author Tamas Szabo
339 *
340 */
341 protected class AggregatorOuterIdentityIndexer extends StandardIndexer {
342
343 protected final int resultPositionInSignature;
344 protected final TupleMask pruneResult;
345 protected final TupleMask reorderMask;
346 /**
347 * @since 2.4
348 */
349 protected NetworkStructureChangeSensitiveLogic logic;
350
351 public AggregatorOuterIdentityIndexer(final int resultPositionInSignature) {
352 super(AbstractColumnAggregatorNode.this.reteContainer,
353 TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1));
354 this.resultPositionInSignature = resultPositionInSignature;
355 this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1);
356 if (resultPositionInSignature == sourceWidth) {
357 this.reorderMask = null;
358 } else {
359 this.reorderMask = mask;
360 }
361 this.logic = createLogic();
362 }
363
364 @Override
365 public void networkStructureChanged() {
366 super.networkStructureChanged();
367 this.logic = createLogic();
368 }
369
370 @Override
371 public Collection<Tuple> get(final Tuple signatureWithResult) {
372 return this.logic.get(signatureWithResult);
373 }
374
375 /**
376 * @since 2.4
377 */
378 @Override
379 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
380 return this.logic.getTimeline(signature);
381 }
382
383 /**
384 * @since 2.4
385 */
386 public void propagate(final Direction direction, final Tuple tuple, final Tuple group,
387 final Timestamp timestamp) {
388 if (tuple != null) {
389 propagate(direction, reorder(tuple), group, true, timestamp);
390 }
391 }
392
393 private Tuple reorder(final Tuple signatureWithResult) {
394 Tuple transformed;
395 if (reorderMask == null) {
396 transformed = signatureWithResult;
397 } else {
398 transformed = reorderMask.transform(signatureWithResult);
399 }
400 return transformed;
401 }
402
403 @Override
404 public Node getActiveNode() {
405 return this.parent;
406 }
407
408 /**
409 * @since 2.4
410 */
411 protected NetworkStructureChangeSensitiveLogic createLogic() {
412 if (this.reteContainer.isTimelyEvaluation()
413 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
414 return this.TIMELY;
415 } else {
416 return this.TIMELESS;
417 }
418 }
419
420 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
421
422 @Override
423 public Collection<Tuple> get(final Tuple signatureWithResult) {
424 final Tuple prunedSignature = pruneResult.transform(signatureWithResult);
425 final AggregateResult result = getAggregateResult(prunedSignature);
426 if (result != null && Objects.equals(signatureWithResult.get(resultPositionInSignature), result)) {
427 return Collections.singleton(signatureWithResult);
428 } else {
429 return null;
430 }
431 }
432
433 @Override
434 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
435 throw new UnsupportedOperationException();
436 }
437
438 };
439
440 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
441
442 @Override
443 public Collection<Tuple> get(final Tuple signatureWithResult) {
444 return TIMELESS.get(signatureWithResult);
445 }
446
447 @Override
448 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signatureWithResult) {
449 final Tuple prunedSignature = pruneResult.transform(signatureWithResult);
450 final Map<AggregateResult, Timeline<Timestamp>> result = getAggregateResultTimeline(prunedSignature);
451 for (final Entry<AggregateResult, Timeline<Timestamp>> entry : result.entrySet()) {
452 if (Objects.equals(signatureWithResult.get(resultPositionInSignature), entry.getKey())) {
453 return Collections.singletonMap(signatureWithResult, entry.getValue());
454 }
455 }
456 return null;
457 }
458
459 };
460
461 }
462
463 /**
464 * @since 2.4
465 */
466 protected static abstract class NetworkStructureChangeSensitiveLogic {
467
468 public abstract Collection<Tuple> get(final Tuple signatureWithResult);
469
470 public abstract Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature);
471
472 }
473
474}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java
new file mode 100644
index 00000000..4480aed8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java
@@ -0,0 +1,369 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation;
10
11import java.util.Map;
12import java.util.Map.Entry;
13
14import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
15import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
21import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver;
22import tools.refinery.viatra.runtime.rete.network.RederivableNode;
23import tools.refinery.viatra.runtime.rete.network.ReteContainer;
24import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup;
27import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
28import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
29import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox;
30
31/**
32 * Timeless implementation of the column aggregator node.
33 * <p>
34 * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the
35 * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached
36 * during the evaluation.
37 *
38 * @author Gabor Bergmann
39 * @author Tamas Szabo
40 * @since 1.4
41 */
42public class ColumnAggregatorNode<Domain, Accumulator, AggregateResult>
43 extends AbstractColumnAggregatorNode<Domain, Accumulator, AggregateResult>
44 implements RederivableNode, PosetAwareReceiver {
45
46 /**
47 * @since 1.6
48 */
49 protected final IPosetComparator posetComparator;
50
51 /**
52 * @since 1.6
53 */
54 protected final boolean deleteRederiveEvaluation;
55
56 // invariant: neutral values are not stored
57 /**
58 * @since 1.6
59 */
60 protected final Map<Tuple, Accumulator> memory;
61 /**
62 * @since 1.6
63 */
64 protected final Map<Tuple, Accumulator> rederivableMemory;
65
66 /**
67 * @since 1.7
68 */
69 protected CommunicationGroup currentGroup;
70
71 /**
72 * Creates a new column aggregator node.
73 *
74 * @param reteContainer
75 * the RETE container of the node
76 * @param operator
77 * the aggregation operator
78 * @param deleteRederiveEvaluation
79 * true if the node should run in DRED mode, false otherwise
80 * @param groupMask
81 * the mask that masks a tuple to obtain the key that we are grouping-by
82 * @param columnMask
83 * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over
84 * @param posetComparator
85 * the poset comparator for the column, if known, otherwise it can be null
86 * @since 1.6
87 */
88 public ColumnAggregatorNode(final ReteContainer reteContainer,
89 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
90 final boolean deleteRederiveEvaluation, final TupleMask groupMask, final TupleMask columnMask,
91 final IPosetComparator posetComparator) {
92 super(reteContainer, operator, groupMask, columnMask);
93 this.memory = CollectionsFactory.createMap();
94 this.rederivableMemory = CollectionsFactory.createMap();
95 this.deleteRederiveEvaluation = deleteRederiveEvaluation;
96 this.posetComparator = posetComparator;
97 // mailbox MUST be instantiated after the fields are all set
98 this.mailbox = instantiateMailbox();
99 }
100
101 /**
102 * Creates a new column aggregator node.
103 *
104 * @param reteContainer
105 * the RETE container of the node
106 * @param operator
107 * the aggregation operator
108 * @param groupMask
109 * the mask that masks a tuple to obtain the key that we are grouping-by
110 * @param aggregatedColumn
111 * the index of the column that the aggregator node is aggregating over
112 */
113 public ColumnAggregatorNode(final ReteContainer reteContainer,
114 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
115 final TupleMask groupMask, final int aggregatedColumn) {
116 this(reteContainer, operator, false, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth),
117 null);
118 }
119
120 @Override
121 public boolean isInDRedMode() {
122 return this.deleteRederiveEvaluation;
123 }
124
125 @Override
126 protected Mailbox instantiateMailbox() {
127 if (groupMask != null && columnMask != null && posetComparator != null) {
128 return new PosetAwareMailbox(this, this.reteContainer);
129 } else {
130 return new BehaviorChangingMailbox(this, this.reteContainer);
131 }
132 }
133
134 @Override
135 public TupleMask getCoreMask() {
136 return groupMask;
137 }
138
139 @Override
140 public TupleMask getPosetMask() {
141 return columnMask;
142 }
143
144 @Override
145 public IPosetComparator getPosetComparator() {
146 return posetComparator;
147 }
148
149 @Override
150 public void rederiveOne() {
151 final Entry<Tuple, Accumulator> entry = rederivableMemory.entrySet().iterator().next();
152 final Tuple group = entry.getKey();
153 final Accumulator accumulator = entry.getValue();
154 rederivableMemory.remove(group);
155 memory.put(group, accumulator);
156 // unregister the node if there is nothing left to be re-derived
157 if (this.rederivableMemory.isEmpty()) {
158 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
159 }
160 final AggregateResult value = operator.getAggregate(accumulator);
161 propagateAggregateResultUpdate(group, NEUTRAL, value, Timestamp.ZERO);
162 }
163
164 @Override
165 public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) {
166 if (this.deleteRederiveEvaluation) {
167 updateWithDeleteAndRederive(direction, update, monotone);
168 } else {
169 updateDefault(direction, update, Timestamp.ZERO);
170 }
171 }
172
173 @Override
174 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
175 updateWithPosetInfo(direction, update, false);
176 }
177
178 /**
179 * @since 2.4
180 */
181 protected void updateDefault(final Direction direction, final Tuple update, final Timestamp timestamp) {
182 final Tuple key = groupMask.transform(update);
183 final Tuple value = columnMask.transform(update);
184 @SuppressWarnings("unchecked")
185 final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0));
186 final boolean isInsertion = direction == Direction.INSERT;
187
188 final Accumulator oldMainAccumulator = getMainAccumulator(key);
189 final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator);
190
191 final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, isInsertion);
192 storeIfNotNeutral(key, newMainAccumulator, memory);
193 final AggregateResult newValue = operator.getAggregate(newMainAccumulator);
194
195 propagateAggregateResultUpdate(key, oldValue, newValue, timestamp);
196 }
197
198 /**
199 * @since 2.4
200 */
201 protected void updateWithDeleteAndRederive(final Direction direction, final Tuple update, final boolean monotone) {
202 final Tuple group = groupMask.transform(update);
203 final Tuple value = columnMask.transform(update);
204 @SuppressWarnings("unchecked")
205 final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0));
206 final boolean isInsertion = direction == Direction.INSERT;
207
208 Accumulator oldMainAccumulator = memory.get(group);
209 Accumulator oldRederivableAccumulator = rederivableMemory.get(group);
210
211 if (direction == Direction.INSERT) {
212 // INSERT
213 if (oldRederivableAccumulator != null) {
214 // the group is in the re-derivable memory
215 final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator,
216 aggregableValue, isInsertion);
217 storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory);
218 if (rederivableMemory.isEmpty()) {
219 // there is nothing left to be re-derived
220 // this can happen if the accumulator became neutral in response to the INSERT
221 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
222 }
223 } else {
224 // the group is in the main memory
225 // at this point, it can happen that we need to initialize with a neutral accumulator
226 if (oldMainAccumulator == null) {
227 oldMainAccumulator = operator.createNeutral();
228 }
229
230 final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator);
231 final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue,
232 isInsertion);
233 storeIfNotNeutral(group, newMainAccumulator, memory);
234 final AggregateResult newValue = operator.getAggregate(newMainAccumulator);
235 propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO);
236 }
237 } else {
238 // DELETE
239 if (oldRederivableAccumulator != null) {
240 // the group is in the re-derivable memory
241 if (oldMainAccumulator != null) {
242 issueError("[INTERNAL ERROR] Inconsistent state for " + update
243 + " because it is present both in the main and re-derivable memory in the ColumnAggregatorNode "
244 + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null);
245 }
246 try {
247 final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator,
248 aggregableValue, isInsertion);
249 storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory);
250 if (rederivableMemory.isEmpty()) {
251 // there is nothing left to be re-derived
252 // this can happen if the accumulator became neutral in response to the DELETE
253 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
254 }
255 } catch (final NullPointerException ex) {
256 issueError("[INTERNAL ERROR] Deleting a domain element in " + update
257 + " which did not exist before in ColumnAggregatorNode " + this + " for pattern(s) "
258 + getTraceInfoPatternsEnumerated(), ex);
259 }
260 } else {
261 // the group is in the main memory
262 // at this point, it can happen that we need to initialize with a neutral accumulator
263 if (oldMainAccumulator == null) {
264 oldMainAccumulator = operator.createNeutral();
265 }
266
267 final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator);
268 final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue,
269 isInsertion);
270 final AggregateResult newValue = operator.getAggregate(newMainAccumulator);
271
272 if (monotone) {
273 storeIfNotNeutral(group, newMainAccumulator, memory);
274 propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO);
275 } else {
276 final boolean wasEmpty = rederivableMemory.isEmpty();
277 if (storeIfNotNeutral(group, newMainAccumulator, rederivableMemory) && wasEmpty) {
278 ((RecursiveCommunicationGroup) currentGroup).addRederivable(this);
279 }
280 memory.remove(group);
281 propagateAggregateResultUpdate(group, oldValue, NEUTRAL, Timestamp.ZERO);
282 }
283 }
284 }
285 }
286
287 @Override
288 public void clear() {
289 this.memory.clear();
290 this.rederivableMemory.clear();
291 this.childMailboxes.clear();
292 }
293
294 /**
295 * Returns true if the accumulator was stored, false otherwise.
296 *
297 * @since 1.6
298 */
299 protected boolean storeIfNotNeutral(final Tuple key, final Accumulator accumulator,
300 final Map<Tuple, Accumulator> memory) {
301 if (operator.isNeutral(accumulator)) {
302 memory.remove(key);
303 return false;
304 } else {
305 memory.put(key, accumulator);
306 return true;
307 }
308 }
309
310 @Override
311 public Tuple getAggregateTuple(final Tuple group) {
312 final Accumulator accumulator = getMainAccumulator(group);
313 final AggregateResult result = operator.getAggregate(accumulator);
314 return tupleFromAggregateResult(group, result);
315 }
316
317 @Override
318 public AggregateResult getAggregateResult(final Tuple group) {
319 final Accumulator accumulator = getMainAccumulator(group);
320 return operator.getAggregate(accumulator);
321 }
322
323 @Override
324 public Map<AggregateResult, Timeline<Timestamp>> getAggregateResultTimeline(Tuple key) {
325 throw new UnsupportedOperationException();
326 }
327
328 @Override
329 public Map<Tuple, Timeline<Timestamp>> getAggregateTupleTimeline(Tuple key) {
330 throw new UnsupportedOperationException();
331 }
332
333 /**
334 * @since 1.6
335 */
336 protected Accumulator getMainAccumulator(final Tuple key) {
337 return getAccumulator(key, memory);
338 }
339
340 /**
341 * @since 1.6
342 */
343 protected Accumulator getRederivableAccumulator(final Tuple key) {
344 return getAccumulator(key, rederivableMemory);
345 }
346
347 /**
348 * @since 1.6
349 */
350 protected Accumulator getAccumulator(final Tuple key, final Map<Tuple, Accumulator> memory) {
351 Accumulator accumulator = memory.get(key);
352 if (accumulator == null) {
353 return operator.createNeutral();
354 } else {
355 return accumulator;
356 }
357 }
358
359 @Override
360 public CommunicationGroup getCurrentGroup() {
361 return currentGroup;
362 }
363
364 @Override
365 public void setCurrentGroup(final CommunicationGroup currentGroup) {
366 this.currentGroup = currentGroup;
367 }
368
369}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java
new file mode 100644
index 00000000..7c98de0d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.aggregation;
11
12import java.util.Collection;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.rete.network.ReteContainer;
16
17/**
18 * An aggregation node that simply counts the number of tuples conforming to the signature.
19 *
20 * @author Gabor Bergmann
21 * @since 1.4
22 */
23public class CountNode extends IndexerBasedAggregatorNode {
24
25 public CountNode(ReteContainer reteContainer) {
26 super(reteContainer);
27 }
28
29 int sizeOf(Collection<Tuple> group) {
30 return group == null ? 0 : group.size();
31 }
32
33 @Override
34 public Object aggregateGroup(Tuple signature, Collection<Tuple> group) {
35 return sizeOf(group);
36 }
37
38}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java
new file mode 100644
index 00000000..3c7850de
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java
@@ -0,0 +1,120 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation;
10
11import java.util.AbstractMap.SimpleEntry;
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19
20/**
21 * An optimized {@link Map} implementation where each key is produced by joining together a group tuple and some other
22 * object (via left inheritance). Only a select few {@link Map} operations are supported. This collection is
23 * unmodifiable.
24 *
25 * Operations on this map assume that client queries also obey the contract that keys are constructed from a group tuple
26 * and an additional object.
27 *
28 * @author Tamas Szabo
29 * @since 2.4
30 */
31public class GroupedMap<GroupedKeyType, ValueType> implements Map<Tuple, ValueType> {
32
33 protected final Tuple group;
34 // cached group size value is to be used in get()
35 private final int groupSize;
36 protected final Map<GroupedKeyType, ValueType> mappings;
37 protected final IQueryRuntimeContext runtimeContext;
38
39 public GroupedMap(final Tuple group, final Map<GroupedKeyType, ValueType> mappings,
40 final IQueryRuntimeContext runtimeContext) {
41 this.group = group;
42 this.groupSize = group.getSize();
43 this.mappings = mappings;
44 this.runtimeContext = runtimeContext;
45 }
46
47 @Override
48 public int size() {
49 return this.mappings.size();
50 }
51
52 @Override
53 public boolean isEmpty() {
54 return this.mappings.isEmpty();
55 }
56
57 @Override
58 public boolean containsKey(final Object key) {
59 throw new UnsupportedOperationException();
60 }
61
62 @Override
63 public boolean containsValue(final Object value) {
64 return this.mappings.containsValue(value);
65 }
66
67 @Override
68 public ValueType get(final Object key) {
69 if (key instanceof Tuple) {
70 final Object value = ((Tuple) key).get(this.groupSize);
71 final Object unwrappedValue = this.runtimeContext.unwrapElement(value);
72 return this.mappings.get(unwrappedValue);
73 } else {
74 return null;
75 }
76 }
77
78 @Override
79 public ValueType put(final Tuple key, final ValueType value) {
80 throw new UnsupportedOperationException();
81 }
82
83 @Override
84 public ValueType remove(final Object key) {
85 throw new UnsupportedOperationException();
86 }
87
88 @Override
89 public void putAll(final Map<? extends Tuple, ? extends ValueType> map) {
90 throw new UnsupportedOperationException();
91 }
92
93 @Override
94 public void clear() {
95 throw new UnsupportedOperationException();
96 }
97
98 @Override
99 public Set<Tuple> keySet() {
100 return new GroupedSet<Tuple, GroupedKeyType, Tuple>(this.group, this.mappings.keySet(), (g, v) -> {
101 return Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v));
102 });
103 }
104
105 @Override
106 public Collection<ValueType> values() {
107 return this.mappings.values();
108 }
109
110 @Override
111 public Set<Entry<Tuple, ValueType>> entrySet() {
112 return new GroupedSet<Tuple, GroupedKeyType, Entry<Tuple, ValueType>>(this.group, this.mappings.keySet(),
113 (g, v) -> {
114 final Tuple key = Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v));
115 final ValueType value = this.mappings.get(v);
116 return new SimpleEntry<Tuple, ValueType>(key, value);
117 });
118 }
119
120} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java
new file mode 100644
index 00000000..65561e53
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation;
10
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.Set;
14import java.util.function.BiFunction;
15
16/**
17 * An optimized {@link Set} implementation where each contained value is produced by combining together a grouping value
18 * and some other (key) object. The way of combining together these two values is specified by the closure passed to the
19 * constructor. Only a select few {@link Set} operations are supported. This collection is unmodifiable.
20 *
21 * @author Tamas Szabo
22 * @since 2.4
23 */
24public class GroupedSet<GroupingValueType, GroupedKeyType, WholeKeyType> implements Set<WholeKeyType> {
25
26 protected final GroupingValueType group;
27 protected final Collection<GroupedKeyType> values;
28 protected final BiFunction<GroupingValueType, GroupedKeyType, WholeKeyType> valueFunc;
29
30 public GroupedSet(final GroupingValueType group, final Collection<GroupedKeyType> values,
31 final BiFunction<GroupingValueType, GroupedKeyType, WholeKeyType> valueFunc) {
32 this.group = group;
33 this.values = values;
34 this.valueFunc = valueFunc;
35 }
36
37 @Override
38 public int size() {
39 return this.values.size();
40 }
41
42 @Override
43 public boolean isEmpty() {
44 return this.values.isEmpty();
45 }
46
47 @Override
48 public boolean contains(final Object obj) {
49 throw new UnsupportedOperationException();
50 }
51
52 @Override
53 public Iterator<WholeKeyType> iterator() {
54 final Iterator<GroupedKeyType> wrapped = this.values.iterator();
55 return new Iterator<WholeKeyType>() {
56 @Override
57 public boolean hasNext() {
58 return wrapped.hasNext();
59 }
60
61 @Override
62 public WholeKeyType next() {
63 final GroupedKeyType value = wrapped.next();
64 return valueFunc.apply(group, value);
65 }
66 };
67 }
68
69 @Override
70 public Object[] toArray() {
71 throw new UnsupportedOperationException();
72 }
73
74 @Override
75 public <T> T[] toArray(final T[] arr) {
76 throw new UnsupportedOperationException();
77 }
78
79 @Override
80 public boolean add(final WholeKeyType tuple) {
81 throw new UnsupportedOperationException();
82 }
83
84 @Override
85 public boolean remove(final Object obj) {
86 throw new UnsupportedOperationException();
87 }
88
89 @Override
90 public boolean containsAll(final Collection<?> c) {
91 throw new UnsupportedOperationException();
92 }
93
94 @Override
95 public boolean addAll(final Collection<? extends WholeKeyType> coll) {
96 throw new UnsupportedOperationException();
97 }
98
99 @Override
100 public boolean retainAll(final Collection<?> coll) {
101 throw new UnsupportedOperationException();
102 }
103
104 @Override
105 public boolean removeAll(final Collection<?> coll) {
106 throw new UnsupportedOperationException();
107 }
108
109 @Override
110 public void clear() {
111 throw new UnsupportedOperationException();
112 }
113
114} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java
new file mode 100644
index 00000000..6c286364
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation;
10
11import tools.refinery.viatra.runtime.rete.index.Indexer;
12
13/**
14 * Expresses that aggregators expose specialized non-enumerable indexers for outer joining.
15 * @author Gabor Bergmann
16 *
17 * @since 1.4
18 *
19 */
20public interface IAggregatorNode {
21
22 Indexer getAggregatorOuterIndexer();
23
24 Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature);
25
26} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java
new file mode 100644
index 00000000..d9a94a82
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java
@@ -0,0 +1,278 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.aggregation;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.Direction;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
23import tools.refinery.viatra.runtime.rete.index.DefaultIndexerListener;
24import tools.refinery.viatra.runtime.rete.index.Indexer;
25import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
26import tools.refinery.viatra.runtime.rete.index.StandardIndexer;
27import tools.refinery.viatra.runtime.rete.network.Node;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.StandardNode;
30import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
31import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
32
33/**
34 * A special node depending on a projection indexer to aggregate tuple groups with the same projection. Only propagates
35 * the aggregates of non-empty groups. Use the outer indexers to circumvent.
36 * <p>
37 * This node cannot be used in recursive differential dataflow evaluation.
38 *
39 * @author Gabor Bergmann
40 * @since 1.4
41 */
42public abstract class IndexerBasedAggregatorNode extends StandardNode implements IAggregatorNode {
43
44 ProjectionIndexer projection;
45 IndexerBasedAggregatorNode me;
46 int sourceWidth;
47 Map<Tuple, Object> mainAggregates;
48
49 AggregatorOuterIndexer aggregatorOuterIndexer = null;
50 AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers = null;
51
52 /**
53 * MUST call initializeWith() afterwards!
54 */
55 public IndexerBasedAggregatorNode(ReteContainer reteContainer) {
56 super(reteContainer);
57 this.me = this;
58 mainAggregates = CollectionsFactory.createMap();
59 }
60
61 @Override
62 public void networkStructureChanged() {
63 if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
64 throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!");
65 }
66 super.networkStructureChanged();
67 }
68
69 /**
70 * @param projection
71 * the projection indexer whose tuple groups should be aggregated
72 */
73 public void initializeWith(ProjectionIndexer projection) {
74 this.projection = projection;
75 this.sourceWidth = projection.getMask().indices.length;
76
77 for (Tuple signature : projection.getSignatures()) {
78 mainAggregates.put(signature, aggregateGroup(signature, projection.get(signature)));
79 }
80 projection.attachListener(new DefaultIndexerListener(this) {
81 @Override
82 public void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) {
83 aggregateUpdate(direction, updateElement, signature, change);
84 }
85 });
86 }
87
88 /**
89 * Aggregates (reduces) a group of tuples. The group can be null.
90 */
91 public abstract Object aggregateGroup(Tuple signature, Collection<Tuple> group);
92
93
94 /**
95 * Aggregates (reduces) a group of tuples, having access to the previous aggregated value (before the update) and
96 * the update definition. Defaults to aggregateGroup(). Override to increase performance.
97 * @since 2.4
98 */
99 public Object aggregateGroupAfterUpdate(Tuple signature, Collection<Tuple> currentGroup, Object oldAggregate,
100 Direction direction, Tuple updateElement, boolean change) {
101 return aggregateGroup(signature, currentGroup);
102 }
103
104 protected Tuple aggregateAndPack(Tuple signature, Collection<Tuple> group) {
105 return packResult(signature, aggregateGroup(signature, group));
106 }
107
108 @Override
109 public Indexer getAggregatorOuterIndexer() {
110 if (aggregatorOuterIndexer == null) {
111 aggregatorOuterIndexer = new AggregatorOuterIndexer();
112 this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer);
113 // reteContainer.connectAndSynchronize(this, aggregatorOuterIndexer);
114 }
115 return aggregatorOuterIndexer;
116 }
117
118 @Override
119 public Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature) {
120 if (aggregatorOuterIdentityIndexers == null)
121 aggregatorOuterIdentityIndexers = new AggregatorOuterIdentityIndexer[sourceWidth + 1];
122 if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) {
123 aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer(
124 resultPositionInSignature);
125 this.getCommunicationTracker().registerDependency(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]);
126 // reteContainer.connectAndSynchronize(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]);
127 }
128 return aggregatorOuterIdentityIndexers[resultPositionInSignature];
129 }
130
131 @Override
132 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
133 for (final Entry<Tuple, Object> aggregateEntry : mainAggregates.entrySet()) {
134 collector.add(packResult(aggregateEntry.getKey(), aggregateEntry.getValue()));
135 }
136 }
137
138 @Override
139 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
140 // use all zero timestamps because this node cannot be used in recursive groups anyway
141 for (final Entry<Tuple, Object> aggregateEntry : mainAggregates.entrySet()) {
142 collector.put(packResult(aggregateEntry.getKey(), aggregateEntry.getValue()), Timestamp.INSERT_AT_ZERO_TIMELINE);
143 }
144 }
145
146 protected Tuple packResult(Tuple signature, Object result) {
147 return Tuples.staticArityLeftInheritanceTupleOf(signature, result);
148 }
149
150 /**
151 * @since 2.4
152 */
153 protected void aggregateUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change) {
154 Collection<Tuple> currentGroup = projection.get(signature);
155 // these will be null if group is empty
156 Object oldAggregate = mainAggregates.get(signature);
157 Object safeOldAggregate = oldAggregate == null ? aggregateGroup(signature, null) : oldAggregate;
158 boolean empty = currentGroup == null || currentGroup.isEmpty();
159 Object newAggregate = empty ? null : aggregateGroupAfterUpdate(signature, currentGroup, safeOldAggregate/*
160 * non-null
161 */,
162 direction, updateElement, change);
163 if (!empty)
164 mainAggregates.put(signature, newAggregate);
165 else
166 mainAggregates.remove(signature);
167 Tuple oldTuple = packResult(signature, safeOldAggregate);
168 Tuple newTuple = packResult(signature, newAggregate == null ? aggregateGroup(signature, null) : newAggregate);
169 if (oldAggregate != null)
170 propagateUpdate(Direction.DELETE, oldTuple, Timestamp.ZERO); // direct outputs lack non-empty groups
171 if (newAggregate != null)
172 propagateUpdate(Direction.INSERT, newTuple, Timestamp.ZERO); // direct outputs lack non-empty groups
173 if (aggregatorOuterIndexer != null)
174 aggregatorOuterIndexer.propagate(signature, oldTuple, newTuple);
175 if (aggregatorOuterIdentityIndexers != null)
176 for (AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers)
177 if (aggregatorOuterIdentityIndexer != null)
178 aggregatorOuterIdentityIndexer.propagate(signature, oldTuple, newTuple);
179 }
180
181 private Object getAggregate(Tuple signature) {
182 Object aggregate = mainAggregates.get(signature);
183 return aggregate == null ? aggregateGroup(signature, null) : aggregate;
184 }
185
186 @Override
187 public void assignTraceInfo(TraceInfo traceInfo) {
188 super.assignTraceInfo(traceInfo);
189 if (traceInfo.propagateToIndexerParent() && projection != null)
190 projection.acceptPropagatedTraceInfo(traceInfo);
191 }
192
193 /**
194 * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original
195 * signature.
196 *
197 * @author Gabor Bergmann
198 */
199 class AggregatorOuterIndexer extends StandardIndexer {
200
201 public AggregatorOuterIndexer() {
202 super(me.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1));
203 this.parent = me;
204 }
205
206 @Override
207 public Collection<Tuple> get(Tuple signature) {
208 return Collections.singleton(packResult(signature, getAggregate(signature)));
209 }
210
211 public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) {
212 propagate(Direction.INSERT, newTuple, signature, false, Timestamp.ZERO);
213 propagate(Direction.DELETE, oldTuple, signature, false, Timestamp.ZERO);
214 }
215
216 @Override
217 public Node getActiveNode() {
218 return projection.getActiveNode();
219 }
220
221 }
222
223 /**
224 * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for
225 * this index is the original signature of the projection index, with the suspected result inserted at position
226 * resultPositionInSignature.
227 *
228 * @author Gabor Bergmann
229 */
230
231 class AggregatorOuterIdentityIndexer extends StandardIndexer {
232 int resultPositionInSignature;
233 TupleMask pruneResult;
234 TupleMask reorderMask;
235
236 public AggregatorOuterIdentityIndexer(int resultPositionInSignature) {
237 super(me.reteContainer, TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1));
238 this.parent = me;
239 // this.localAggregates = new HashMap<Tuple, Tuple>();
240 this.resultPositionInSignature = resultPositionInSignature;
241 this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1);
242 if (resultPositionInSignature == sourceWidth)
243 this.reorderMask = null;
244 else
245 this.reorderMask = mask;
246 }
247
248 @Override
249 public Collection<Tuple> get(Tuple signatureWithResult) {
250 Tuple prunedSignature = pruneResult.transform(signatureWithResult);
251 Object result = getAggregate(prunedSignature);
252 if (signatureWithResult.get(resultPositionInSignature).equals(result))
253 return Collections.singleton(signatureWithResult);
254 else
255 return null;
256 }
257
258 public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) {
259 propagate(Direction.INSERT, reorder(newTuple), signature, true, Timestamp.ZERO);
260 propagate(Direction.DELETE, reorder(oldTuple), signature, true, Timestamp.ZERO);
261 }
262
263 private Tuple reorder(Tuple signatureWithResult) {
264 Tuple transformed;
265 if (reorderMask == null)
266 transformed = signatureWithResult;
267 else
268 transformed = reorderMask.transform(signatureWithResult);
269 return transformed;
270 }
271
272 @Override
273 public Node getActiveNode() {
274 return projection.getActiveNode();
275 }
276 }
277
278}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..19e02f10
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java
@@ -0,0 +1,212 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
14import tools.refinery.viatra.runtime.matchers.util.*;
15import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
16import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.CumulativeAggregate;
17import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.FoldingState;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
21
22import java.util.Collections;
23import java.util.Map;
24import java.util.Map.Entry;
25import java.util.Objects;
26import java.util.TreeMap;
27
28/**
29 * Faithful column aggregator with parallel aggregation architecture.
30 *
31 * @author Tamas Szabo
32 * @since 2.4
33 *
34 */
35public class FaithfulParallelTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult> extends
36 FaithfulTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult, CumulativeAggregate<Domain, Accumulator>, FoldingState<Domain>>
37 implements ResumableNode {
38
39 public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer,
40 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
41 final TupleMask groupMask, final TupleMask columnMask) {
42 super(reteContainer, operator, groupMask, columnMask);
43 }
44
45 public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer,
46 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
47 final TupleMask groupMask, final int aggregatedColumn) {
48 this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth));
49 }
50
51 @Override
52 protected Map<AggregateResult, Diff<Timestamp>> doFoldingStep(final Tuple group, final FoldingState<Domain> state,
53 final Timestamp timestamp) {
54 final CumulativeAggregate<Domain, Accumulator> aggregate = getAggregate(group, timestamp);
55 if (state.delta.isEmpty()) {
56 gcAggregates(aggregate, group, timestamp);
57 return Collections.emptyMap();
58 } else {
59 final Map<AggregateResult, Diff<Timestamp>> diffMap = CollectionsFactory.createMap();
60 final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp);
61
62 final AggregateResult currentOldResult = operator.getAggregate(aggregate.accumulator);
63
64 for (final Entry<Domain, Integer> entry : state.delta.entriesWithMultiplicities()) {
65 final boolean isInsertion = entry.getValue() > 0;
66 final Domain aggregand = entry.getKey();
67 for (int i = 0; i < Math.abs(entry.getValue()); i++) {
68 aggregate.accumulator = operator.update(aggregate.accumulator, aggregand, isInsertion);
69 }
70 }
71
72 final AggregateResult currentNewResult = operator.getAggregate(aggregate.accumulator);
73
74 if (!Objects.equals(currentOldResult, currentNewResult)) {
75 // current old result disappears here
76 appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap);
77 if (nextTimestamp != null) {
78 appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap);
79 }
80
81 // current new result appears here
82 appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap);
83 if (nextTimestamp != null) {
84 appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap);
85 }
86 }
87
88 gcAggregates(aggregate, group, timestamp);
89 updateTimeline(group, diffMap);
90
91 // prepare folding state for next timestamp
92 if (nextTimestamp != null) {
93 final FoldingState<Domain> newState = new FoldingState<>();
94 newState.delta = state.delta;
95 addFoldingState(group, newState, nextTimestamp);
96 }
97
98 return diffMap;
99 }
100 }
101
102 @Override
103 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
104 final Tuple group = groupMask.transform(update);
105 final Tuple value = columnMask.transform(update);
106 @SuppressWarnings("unchecked")
107 final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0));
108 final boolean isInsertion = direction == Direction.INSERT;
109
110 final CumulativeAggregate<Domain, Accumulator> aggregate = getAggregate(group, timestamp);
111 final FoldingState<Domain> state = new FoldingState<>();
112 if (isInsertion) {
113 aggregate.aggregands.addOne(aggregand);
114 state.delta.addOne(aggregand);
115 } else {
116 aggregate.aggregands.removeOne(aggregand);
117 state.delta.removeOne(aggregand);
118 }
119
120 addFoldingState(group, state, timestamp);
121 }
122
123 /**
124 * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty.
125 */
126 @Override
127 protected void gcAggregates(final CumulativeAggregate<Domain, Accumulator> aggregate, final Tuple group,
128 final Timestamp timestamp) {
129 if (aggregate.aggregands.isEmpty()) {
130 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator>> groupAggregates = this.aggregates
131 .get(group);
132 groupAggregates.remove(timestamp);
133 if (groupAggregates.isEmpty()) {
134 this.aggregates.remove(group);
135 }
136 }
137 }
138
139 /**
140 * On-demand initializes and returns the aggregate for the given group and timestamp.
141 */
142 @Override
143 protected CumulativeAggregate<Domain, Accumulator> getAggregate(final Tuple group, final Timestamp timestamp) {
144 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator>> groupAggregates = this.aggregates
145 .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap());
146 return groupAggregates.computeIfAbsent(timestamp, k -> {
147 final CumulativeAggregate<Domain, Accumulator> aggregate = new CumulativeAggregate<>();
148 final Entry<Timestamp, CumulativeAggregate<Domain, Accumulator>> lowerEntry = groupAggregates
149 .lowerEntry(timestamp);
150 if (lowerEntry == null) {
151 aggregate.accumulator = operator.createNeutral();
152 } else {
153 aggregate.accumulator = operator.clone(lowerEntry.getValue().accumulator);
154 }
155 return aggregate;
156 });
157 }
158
159 @Override
160 public AggregateResult getAggregateResult(final Tuple group) {
161 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator>> groupAggregates = this.aggregates.get(group);
162 if (groupAggregates != null) {
163 final Entry<Timestamp, CumulativeAggregate<Domain, Accumulator>> lastEntry = groupAggregates.lastEntry();
164 return operator.getAggregate(lastEntry.getValue().accumulator);
165 } else {
166 return NEUTRAL;
167 }
168 }
169
170 protected static class CumulativeAggregate<Domain, Accumulator> {
171 protected Accumulator accumulator;
172 protected IDeltaBag<Domain> aggregands;
173
174 protected CumulativeAggregate() {
175 this.aggregands = CollectionsFactory.createDeltaBag();
176 }
177
178 @Override
179 public String toString() {
180 return "accumulator=" + accumulator + " aggregands=" + aggregands;
181 }
182 }
183
184 protected static class FoldingState<Domain> implements MergeableFoldingState<FoldingState<Domain>> {
185 protected IDeltaBag<Domain> delta;
186
187 protected FoldingState() {
188 this.delta = CollectionsFactory.createDeltaBag();
189 }
190
191 @Override
192 public String toString() {
193 return "delta=" + delta;
194 }
195
196 /**
197 * The returned result will never be null, even if the resulting delta set is empty.
198 */
199 @Override
200 public FoldingState<Domain> merge(final FoldingState<Domain> that) {
201 Preconditions.checkArgument(that != null);
202 // 'this' was the previously registered folding state
203 // 'that' is the new folding state being pushed upwards
204 final FoldingState<Domain> result = new FoldingState<>();
205 this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m));
206 that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m));
207 return result;
208 }
209
210 }
211
212}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..cf2c2b2d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java
@@ -0,0 +1,279 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Objects;
15import java.util.TreeMap;
16
17import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.Direction;
22import tools.refinery.viatra.runtime.matchers.util.IDeltaBag;
23import tools.refinery.viatra.runtime.matchers.util.Preconditions;
24import tools.refinery.viatra.runtime.matchers.util.Signed;
25import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
26import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.CumulativeAggregate;
27import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.FoldingState;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
30import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
31
32/**
33 * Faithful column aggregator with sequential aggregation architecture.
34 *
35 * @author Tamas Szabo
36 * @since 2.4
37 *
38 */
39public class FaithfulSequentialTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult> extends
40 FaithfulTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult, CumulativeAggregate<Domain, Accumulator, AggregateResult>, FoldingState<Domain, AggregateResult>>
41 implements ResumableNode {
42
43 protected boolean isRecursiveAggregation;
44
45 public FaithfulSequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer,
46 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
47 final TupleMask groupMask, final TupleMask columnMask) {
48 super(reteContainer, operator, groupMask, columnMask);
49 this.isRecursiveAggregation = false;
50 }
51
52 @Override
53 public void networkStructureChanged() {
54 super.networkStructureChanged();
55 this.isRecursiveAggregation = this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this);
56 }
57
58 @Override
59 protected Map<AggregateResult, Diff<Timestamp>> doFoldingStep(final Tuple group,
60 final FoldingState<Domain, AggregateResult> state, final Timestamp timestamp) {
61 final CumulativeAggregate<Domain, Accumulator, AggregateResult> aggregate = getAggregate(group, timestamp);
62 if (state.delta.isEmpty() && Objects.equals(state.oldResult, state.newResult)) {
63 gcAggregates(aggregate, group, timestamp);
64 return Collections.emptyMap();
65 } else {
66 final Map<AggregateResult, Diff<Timestamp>> diffMap = CollectionsFactory.createMap();
67 final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp);
68
69 final AggregateResult previousOldResult = state.oldResult;
70 final AggregateResult previousNewResult = state.newResult;
71
72 final AggregateResult currentOldResult = previousOldResult == null
73 ? operator.getAggregate(aggregate.positive)
74 : operator.combine(previousOldResult, aggregate.positive);
75
76 for (final Entry<Domain, Integer> entry : state.delta.entriesWithMultiplicities()) {
77 final boolean isInsertion = entry.getValue() > 0;
78 final Domain aggregand = entry.getKey();
79 if (isInsertion) {
80 for (int i = 0; i < entry.getValue(); i++) {
81 if (isRecursiveAggregation) {
82 final boolean contains = aggregate.negative.containsNonZero(aggregand);
83 if (contains) {
84 aggregate.negative.addOne(aggregand);
85 } else {
86 aggregate.positive = operator.update(aggregate.positive, aggregand, true);
87 }
88 } else {
89 aggregate.positive = operator.update(aggregate.positive, aggregand, true);
90 }
91 }
92 } else {
93 for (int i = 0; i < -entry.getValue(); i++) {
94 if (isRecursiveAggregation) {
95 final boolean contains = operator.contains(aggregand, aggregate.positive);
96 if (contains) {
97 aggregate.positive = operator.update(aggregate.positive, aggregand, false);
98 } else {
99 aggregate.negative.removeOne(aggregand);
100 }
101 } else {
102 aggregate.positive = operator.update(aggregate.positive, aggregand, false);
103 }
104 }
105 }
106 }
107
108 final AggregateResult currentNewResult = previousNewResult == null
109 ? operator.getAggregate(aggregate.positive)
110 : operator.combine(previousNewResult, aggregate.positive);
111
112 aggregate.cachedResult = currentNewResult;
113
114 final boolean sameResult = Objects.equals(currentOldResult, currentNewResult);
115 if (!sameResult) {
116 // current old result disappears here
117 appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap);
118 if (nextTimestamp != null) {
119 appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap);
120 }
121
122 // current new result appears here
123 appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap);
124 if (nextTimestamp != null) {
125 appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap);
126 }
127 }
128
129 gcAggregates(aggregate, group, timestamp);
130 updateTimeline(group, diffMap);
131
132 // prepare folding state for next timestamp
133 if (nextTimestamp != null && !sameResult) {
134 final FoldingState<Domain, AggregateResult> newState = new FoldingState<>();
135 // DO NOT push forward the delta in the folding state!!! that one only affects the input timestamp
136 newState.oldResult = currentOldResult;
137 newState.newResult = currentNewResult;
138 addFoldingState(group, newState, nextTimestamp);
139 }
140
141 return diffMap;
142 }
143 }
144
145 @Override
146 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
147 final Tuple group = groupMask.transform(update);
148 final Tuple value = columnMask.transform(update);
149 @SuppressWarnings("unchecked")
150 final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0));
151 final boolean isInsertion = direction == Direction.INSERT;
152
153 final AggregateResult previousResult = getResultRaw(group, timestamp, true);
154 final FoldingState<Domain, AggregateResult> state = new FoldingState<Domain, AggregateResult>();
155 if (isInsertion) {
156 state.delta.addOne(aggregand);
157 } else {
158 state.delta.removeOne(aggregand);
159 }
160 state.oldResult = previousResult;
161 state.newResult = previousResult;
162
163 // it is acceptable if both oldResult and newResult are null at this point
164 // in that case we did not have a previous entry at a lower timestamp
165
166 addFoldingState(group, state, timestamp);
167 }
168
169 protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) {
170 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> entryMap = this.aggregates
171 .get(group);
172 if (entryMap == null) {
173 return null;
174 } else {
175 CumulativeAggregate<Domain, Accumulator, AggregateResult> aggregate = null;
176 if (lower) {
177 final Entry<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> lowerEntry = entryMap
178 .lowerEntry(timestamp);
179 if (lowerEntry != null) {
180 aggregate = lowerEntry.getValue();
181 }
182 } else {
183 aggregate = entryMap.get(timestamp);
184 }
185 if (aggregate == null) {
186 return null;
187 } else {
188 return aggregate.cachedResult;
189 }
190 }
191 }
192
193 @Override
194 protected void gcAggregates(final CumulativeAggregate<Domain, Accumulator, AggregateResult> aggregate,
195 final Tuple group, final Timestamp timestamp) {
196 if (operator.isNeutral(aggregate.positive) && aggregate.negative.isEmpty()) {
197 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> groupAggregates = this.aggregates
198 .get(group);
199 groupAggregates.remove(timestamp);
200 if (groupAggregates.isEmpty()) {
201 this.aggregates.remove(group);
202 }
203 }
204 }
205
206 @Override
207 protected CumulativeAggregate<Domain, Accumulator, AggregateResult> getAggregate(final Tuple group,
208 final Timestamp timestamp) {
209 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> groupAggregates = this.aggregates
210 .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap());
211 return groupAggregates.computeIfAbsent(timestamp, k -> {
212 final CumulativeAggregate<Domain, Accumulator, AggregateResult> aggregate = new CumulativeAggregate<>();
213 aggregate.positive = operator.createNeutral();
214 return aggregate;
215 });
216 }
217
218 @Override
219 public AggregateResult getAggregateResult(final Tuple group) {
220 final TreeMap<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> groupAggregates = this.aggregates
221 .get(group);
222 if (groupAggregates != null) {
223 final Entry<Timestamp, CumulativeAggregate<Domain, Accumulator, AggregateResult>> lastEntry = groupAggregates
224 .lastEntry();
225 return lastEntry.getValue().cachedResult;
226 } else {
227 return NEUTRAL;
228 }
229 }
230
231 protected static class CumulativeAggregate<Domain, Accumulator, AggregateResult> {
232 protected Accumulator positive;
233 protected IDeltaBag<Domain> negative;
234 protected AggregateResult cachedResult;
235
236 protected CumulativeAggregate() {
237 this.negative = CollectionsFactory.createDeltaBag();
238 }
239
240 @Override
241 public String toString() {
242 return "positive=" + positive + " negative=" + negative + " cachedResult=" + cachedResult;
243 }
244 }
245
246 protected static class FoldingState<Domain, AggregateResult>
247 implements MergeableFoldingState<FoldingState<Domain, AggregateResult>> {
248 protected IDeltaBag<Domain> delta;
249 protected AggregateResult oldResult;
250 protected AggregateResult newResult;
251
252 protected FoldingState() {
253 this.delta = CollectionsFactory.createDeltaBag();
254 }
255
256 @Override
257 public String toString() {
258 return "delta=" + delta + " oldResult=" + oldResult + " newResult=" + newResult;
259 }
260
261 /**
262 * The returned result will never be null, even if the resulting delta set is empty.
263 */
264 @Override
265 public FoldingState<Domain, AggregateResult> merge(final FoldingState<Domain, AggregateResult> that) {
266 Preconditions.checkArgument(that != null);
267 // 'this' was the previously registered folding state
268 // 'that' is the new folding state being pushed upwards
269 final FoldingState<Domain, AggregateResult> result = new FoldingState<Domain, AggregateResult>();
270 this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m));
271 that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m));
272 result.oldResult = this.oldResult;
273 result.newResult = that.newResult;
274 return result;
275 }
276
277 }
278
279}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..8fe9a4e9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java
@@ -0,0 +1,247 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Objects;
15import java.util.TreeMap;
16
17import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.Signed;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
25import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode;
26import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap;
27import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
30import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
31import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
32import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
33import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
34
35/**
36 * Faithful timely implementation of the column aggregator node. Complete timelines (series of appearance &
37 * disappearance) are maintained for tuples. <br>
38 * <br>
39 * Subclasses are responsible for implementing the aggregator architecture, and they must use the CumulativeAggregate
40 * type parameter for that. <br>
41 * <br>
42 * This node supports recursive aggregation.
43 *
44 * @author Tamas Szabo
45 * @since 2.4
46 */
47public abstract class FaithfulTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult, CumulativeAggregate, FoldingState extends MergeableFoldingState<FoldingState>>
48 extends AbstractColumnAggregatorNode<Domain, Accumulator, AggregateResult> implements ResumableNode {
49
50 protected final Map<Tuple, TreeMap<Timestamp, CumulativeAggregate>> aggregates;
51 protected final Map<Tuple, Map<AggregateResult, Timeline<Timestamp>>> timelines;
52 protected final TreeMap<Timestamp, Map<Tuple, FoldingState>> foldingState;
53 protected CommunicationGroup communicationGroup;
54
55 public FaithfulTimelyColumnAggregatorNode(final ReteContainer reteContainer,
56 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
57 final TupleMask groupMask, final TupleMask columnMask) {
58 super(reteContainer, operator, groupMask, columnMask);
59 this.aggregates = CollectionsFactory.createMap();
60 this.timelines = CollectionsFactory.createMap();
61 this.foldingState = CollectionsFactory.createTreeMap();
62 // mailbox MUST be instantiated after the fields are all set
63 this.mailbox = instantiateMailbox();
64 }
65
66 @Override
67 protected Mailbox instantiateMailbox() {
68 return new TimelyMailbox(this, this.reteContainer);
69 }
70
71 @Override
72 public void clear() {
73 this.mailbox.clear();
74 this.aggregates.clear();
75 this.timelines.clear();
76 this.children.clear();
77 this.childMailboxes.clear();
78 this.foldingState.clear();
79 }
80
81 /**
82 * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the
83 * two states will be merged together.
84 *
85 *
86 */
87 protected void addFoldingState(final Tuple group, final FoldingState state, final Timestamp timestamp) {
88 // assert !state.delta.isEmpty();
89 final Map<Tuple, FoldingState> tupleMap = this.foldingState.computeIfAbsent(timestamp,
90 k -> CollectionsFactory.createMap());
91 tupleMap.compute(group, (k, v) -> {
92 return v == null ? state : v.merge(state);
93 });
94 }
95
96 @Override
97 public Timestamp getResumableTimestamp() {
98 if (this.foldingState.isEmpty()) {
99 return null;
100 } else {
101 return this.foldingState.firstKey();
102 }
103 }
104
105 @Override
106 public void resumeAt(final Timestamp timestamp) {
107 Timestamp current = this.getResumableTimestamp();
108 if (current == null) {
109 throw new IllegalStateException("There is nothing to fold!");
110 } else if (current.compareTo(timestamp) != 0) {
111 throw new IllegalStateException("Expected to continue folding at " + timestamp + "!");
112 }
113
114 final Map<Tuple, FoldingState> tupleMap = this.foldingState.remove(timestamp);
115 for (final Entry<Tuple, FoldingState> groupEntry : tupleMap.entrySet()) {
116 final Tuple group = groupEntry.getKey();
117 final FoldingState value = groupEntry.getValue();
118 final Map<AggregateResult, Diff<Timestamp>> diffMap = doFoldingStep(group, value, timestamp);
119 for (final Entry<AggregateResult, Diff<Timestamp>> resultEntry : diffMap.entrySet()) {
120 for (final Signed<Timestamp> signed : resultEntry.getValue()) {
121 propagate(signed.getDirection(), group, resultEntry.getKey(), signed.getPayload());
122 }
123 }
124 }
125
126 final Timestamp nextTimestamp = this.getResumableTimestamp();
127 if (Objects.equals(timestamp, nextTimestamp)) {
128 throw new IllegalStateException(
129 "Folding at " + timestamp + " produced more folding work at the same timestamp!");
130 } else if (nextTimestamp != null) {
131 this.communicationGroup.notifyHasMessage(this.mailbox, nextTimestamp);
132 }
133 }
134
135 protected abstract Map<AggregateResult, Diff<Timestamp>> doFoldingStep(final Tuple group, final FoldingState state,
136 final Timestamp timestamp);
137
138 /**
139 * Updates and garbage collects the timeline of the given tuple based on the given diffs.
140 */
141 protected void updateTimeline(final Tuple group, final Map<AggregateResult, Diff<Timestamp>> diffs) {
142 if (!diffs.isEmpty()) {
143 this.timelines.compute(group, (k, resultTimelines) -> {
144 if (resultTimelines == null) {
145 resultTimelines = CollectionsFactory.createMap();
146 }
147 for (final Entry<AggregateResult, Diff<Timestamp>> entry : diffs.entrySet()) {
148 final AggregateResult result = entry.getKey();
149 resultTimelines.compute(result, (k2, oldResultTimeline) -> {
150 final Diff<Timestamp> currentResultDiffs = entry.getValue();
151 if (oldResultTimeline == null) {
152 oldResultTimeline = getInitialTimeline(result);
153 }
154 final Timeline<Timestamp> timeline = oldResultTimeline.mergeAdditive(currentResultDiffs);
155 if (timeline.isEmpty()) {
156 return null;
157 } else {
158 return timeline;
159 }
160 });
161 }
162 if (resultTimelines.isEmpty()) {
163 return null;
164 } else {
165 return resultTimelines;
166 }
167 });
168 }
169 }
170
171 /**
172 * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty.
173 */
174 protected abstract void gcAggregates(final CumulativeAggregate aggregate, final Tuple group,
175 final Timestamp timestamp);
176
177 /**
178 * On-demand initializes and returns the aggregate for the given group and timestamp.
179 */
180 protected abstract CumulativeAggregate getAggregate(final Tuple group, final Timestamp timestamp);
181
182 protected static final Timeline<Timestamp> NEUTRAL_INITIAL_TIMELINE = Timestamp.INSERT_AT_ZERO_TIMELINE;
183 protected static final Timeline<Timestamp> NON_NEUTRAL_INITIAL_TIMELINE = Timelines.createEmpty();
184
185 protected Timeline<Timestamp> getInitialTimeline(final AggregateResult result) {
186 if (NEUTRAL == result) {
187 return NEUTRAL_INITIAL_TIMELINE;
188 } else {
189 return NON_NEUTRAL_INITIAL_TIMELINE;
190 }
191 }
192
193 protected static <AggregateResult> void appendDiff(final AggregateResult result, final Signed<Timestamp> diff,
194 final Map<AggregateResult, Diff<Timestamp>> diffs) {
195 if (result != null) {
196 diffs.compute(result, (k, timeLineDiff) -> {
197 if (timeLineDiff == null) {
198 timeLineDiff = new Diff<>();
199 }
200 timeLineDiff.add(diff);
201 return timeLineDiff;
202 });
203 }
204 }
205
206 @Override
207 public Tuple getAggregateTuple(final Tuple group) {
208 return tupleFromAggregateResult(group, getAggregateResult(group));
209 }
210
211 @Override
212 public Map<AggregateResult, Timeline<Timestamp>> getAggregateResultTimeline(final Tuple group) {
213 final Map<AggregateResult, Timeline<Timestamp>> resultTimelines = this.timelines.get(group);
214 if (resultTimelines == null) {
215 if (NEUTRAL == null) {
216 return Collections.emptyMap();
217 } else {
218 return Collections.singletonMap(NEUTRAL, NEUTRAL_INITIAL_TIMELINE);
219 }
220 } else {
221 return resultTimelines;
222 }
223 }
224
225 @Override
226 public Map<Tuple, Timeline<Timestamp>> getAggregateTupleTimeline(final Tuple group) {
227 final Map<AggregateResult, Timeline<Timestamp>> resultTimelines = getAggregateResultTimeline(group);
228 return new GroupedMap<AggregateResult, Timeline<Timestamp>>(group, resultTimelines, this.runtimeContext);
229 }
230
231 @Override
232 public CommunicationGroup getCurrentGroup() {
233 return communicationGroup;
234 }
235
236 @Override
237 public void setCurrentGroup(final CommunicationGroup currentGroup) {
238 this.communicationGroup = currentGroup;
239 }
240
241 protected interface MergeableFoldingState<T> {
242
243 public abstract T merge(final T that);
244
245 }
246
247} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..733d2585
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import java.util.Map.Entry;
12import java.util.TreeMap;
13
14import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20
21/**
22 * First-only column aggregator with parallel aggregation architecture.
23 *
24 * @author Tamas Szabo
25 * @since 2.4
26 */
27public class FirstOnlyParallelTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult>
28 extends FirstOnlyTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult> {
29
30 public FirstOnlyParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer,
31 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
32 final TupleMask groupMask, final TupleMask columnMask) {
33 super(reteContainer, operator, groupMask, columnMask);
34 }
35
36 /**
37 * Accumulator gets modified at the input timestamp and at all higher timestamps. Folding cannot be interrupted if
38 * the new aggregate result is the same as the old at an intermediate timestamp because aggregands need to be copied
39 * over to all accumulators at the higher timestamps.
40 */
41 @Override
42 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
43 final Tuple group = groupMask.transform(update);
44 final Tuple value = columnMask.transform(update);
45 @SuppressWarnings("unchecked")
46 final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0));
47 final boolean isInsertion = direction == Direction.INSERT;
48
49 final AggregateResult previousResult = getResultRaw(group, timestamp, true);
50
51 Accumulator oldAccumulator = getAccumulator(group, timestamp);
52 AggregateResult oldResult = operator.getAggregate(oldAccumulator);
53
54 Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion);
55 AggregateResult newResult = operator.getAggregate(newAccumulator);
56
57 storeIfNotNeutral(group, newAccumulator, newResult, timestamp);
58
59 propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult);
60
61 AggregateResult previousOldResult = oldResult;
62 AggregateResult previousNewResult = newResult;
63 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> groupEntries = this.memory
64 .get(group);
65
66 Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp);
67
68 while (currentTimestamp != null) {
69 final CumulativeAggregate<Accumulator, AggregateResult> groupEntry = groupEntries.get(currentTimestamp);
70 oldResult = groupEntry.result;
71 oldAccumulator = groupEntry.accumulator;
72 newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion);
73 newResult = operator.getAggregate(newAccumulator);
74
75 storeIfNotNeutral(group, newAccumulator, newResult, currentTimestamp);
76
77 propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, oldResult, newResult);
78
79 previousOldResult = oldResult;
80 previousNewResult = newResult;
81 currentTimestamp = groupEntries.higherKey(currentTimestamp);
82 }
83 }
84
85 @Override
86 protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) {
87 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entryMap = this.memory.get(group);
88 if (entryMap == null) {
89 return operator.createNeutral();
90 } else {
91 final CumulativeAggregate<Accumulator, AggregateResult> entry = entryMap.get(timestamp);
92 if (entry == null) {
93 final Entry<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> lowerEntry = entryMap
94 .lowerEntry(timestamp);
95 if (lowerEntry == null) {
96 return operator.createNeutral();
97 } else {
98 return operator.clone(lowerEntry.getValue().accumulator);
99 }
100 } else {
101 return entry.accumulator;
102 }
103 }
104 }
105
106}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..79197aac
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import java.util.Objects;
12import java.util.TreeMap;
13
14import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20
21/**
22 * First-only column aggregator with sequential aggregation architecture.
23 *
24 * @author Tamas Szabo
25 * @since 2.4
26 */
27public class FirstOnlySequentialTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult>
28 extends FirstOnlyTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult> {
29
30 public FirstOnlySequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer,
31 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
32 final TupleMask groupMask, final TupleMask columnMask) {
33 super(reteContainer, operator, groupMask, columnMask);
34 }
35
36 /**
37 * Accumulator gets modified only at the timestamp where the update happened. During the folding, accumulators are
38 * never changed at higher timestamps. Aggregate results at higher timestamps may change due to the change at the
39 * input timestamp. Uniqueness enforcement may require from aggregate results to jump up/down on demand during the
40 * folding.
41 */
42 @Override
43 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
44 final Tuple group = groupMask.transform(update);
45 final Tuple value = columnMask.transform(update);
46 @SuppressWarnings("unchecked")
47 final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0));
48 final boolean isInsertion = direction == Direction.INSERT;
49
50 final AggregateResult previousResult = getResultRaw(group, timestamp, true);
51
52 final Accumulator oldAccumulator = getAccumulator(group, timestamp);
53 final AggregateResult oldResult = previousResult == null ? operator.getAggregate(oldAccumulator)
54 : operator.combine(previousResult, oldAccumulator);
55
56 final Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion);
57 final AggregateResult newResult = previousResult == null ? operator.getAggregate(newAccumulator)
58 : operator.combine(previousResult, newAccumulator);
59
60 storeIfNotNeutral(group, newAccumulator, newResult, timestamp);
61
62 propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult);
63
64 // fold up the state towards higher timestamps
65 if (!Objects.equals(oldResult, newResult)) {
66 AggregateResult previousOldResult = oldResult;
67 AggregateResult previousNewResult = newResult;
68 AggregateResult currentOldResult = null;
69 AggregateResult currentNewResult = null;
70 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> groupEntries = this.memory
71 .get(group);
72
73 Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp);
74
75 while (currentTimestamp != null) {
76 // they cannot be the same, otherwise we would not even be here
77 assert !Objects.equals(previousOldResult, previousNewResult);
78
79 final Accumulator accumulator = getAccumulator(group, currentTimestamp);
80 currentOldResult = groupEntries.get(currentTimestamp).result;
81 currentNewResult = operator.combine(previousNewResult, accumulator);
82
83 // otherwise we would not be iterating over this timestamp
84 assert !operator.isNeutral(accumulator);
85
86 propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, currentOldResult,
87 currentNewResult);
88
89 if (!Objects.equals(currentOldResult, currentNewResult)) {
90 storeIfNotNeutral(group, accumulator, currentNewResult, currentTimestamp);
91 previousOldResult = currentOldResult;
92 previousNewResult = currentNewResult;
93 currentTimestamp = groupEntries.higherKey(currentTimestamp);
94 } else {
95 // we can stop the folding from here
96 break;
97 }
98 }
99 }
100 }
101
102 @Override
103 protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) {
104 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entryMap = this.memory.get(group);
105 if (entryMap == null) {
106 return operator.createNeutral();
107 } else {
108 final CumulativeAggregate<Accumulator, AggregateResult> entry = entryMap.get(timestamp);
109 if (entry == null) {
110 return operator.createNeutral();
111 } else {
112 return entry.accumulator;
113 }
114 }
115 }
116
117}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java
new file mode 100644
index 00000000..0c73000e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java
@@ -0,0 +1,212 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.aggregation.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Objects;
16import java.util.TreeMap;
17
18import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
21import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
22import tools.refinery.viatra.runtime.matchers.util.Direction;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
25import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode;
26import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap;
27import tools.refinery.viatra.runtime.rete.network.ReteContainer;
28import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
29import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
30import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
31
32/**
33 * First-only timely implementation of the column aggregator node. Only timestamps of appearance are maintained for
34 * tuples instead of complete timelines.
35 * <br><br>
36 * Subclasses are responsible for implementing the aggregator architecture, and they must make use of the inner class {@link CumulativeAggregate}.
37 * <br><br>
38 * This node supports recursive aggregation.
39 *
40 * @author Tamas Szabo
41 * @since 2.4
42 */
43public abstract class FirstOnlyTimelyColumnAggregatorNode<Domain, Accumulator, AggregateResult>
44 extends AbstractColumnAggregatorNode<Domain, Accumulator, AggregateResult> {
45
46 protected final Map<Tuple, TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>>> memory;
47
48 public FirstOnlyTimelyColumnAggregatorNode(final ReteContainer reteContainer,
49 final IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> operator,
50 final TupleMask groupMask, final TupleMask columnMask) {
51 super(reteContainer, operator, groupMask, columnMask);
52 this.memory = CollectionsFactory.createMap();
53 // mailbox MUST be instantiated after the fields are all set
54 this.mailbox = instantiateMailbox();
55 }
56
57 protected static class CumulativeAggregate<Accumulator, AggregateResult> {
58 // the accumulator storing the aggregands
59 protected Accumulator accumulator;
60 // the aggregate result at the timestamp where this cumulative aggregate is stored
61 protected AggregateResult result;
62
63 private CumulativeAggregate(final Accumulator accumulator, final AggregateResult result) {
64 this.accumulator = accumulator;
65 this.result = result;
66 }
67
68 }
69
70 public Collection<Tuple> getGroups() {
71 return this.memory.keySet();
72 }
73
74 public AggregateResult getLastResult(final Tuple group) {
75 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> groupMap = this.memory.get(group);
76 if (groupMap == null) {
77 return null;
78 } else {
79 return groupMap.lastEntry().getValue().result;
80 }
81 }
82
83 public Timestamp getLastTimestamp(final Tuple group) {
84 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> groupMap = this.memory.get(group);
85 if (groupMap == null) {
86 return null;
87 } else {
88 return groupMap.lastEntry().getKey();
89 }
90 }
91
92 @Override
93 protected Mailbox instantiateMailbox() {
94 return new TimelyMailbox(this, this.reteContainer);
95 }
96
97 @Override
98 public void clear() {
99 this.mailbox.clear();
100 this.memory.clear();
101 this.children.clear();
102 this.childMailboxes.clear();
103 }
104
105 protected void propagateWithChecks(final Tuple group, final Timestamp timestamp,
106 final AggregateResult previousOldResult, final AggregateResult previousNewResult,
107 final AggregateResult currentOldResult, final AggregateResult currentNewResult) {
108 final boolean jumpDown = Objects.equals(previousNewResult, currentOldResult);
109 final boolean jumpUp = Objects.equals(previousOldResult, currentNewResult);
110 final boolean resultsDiffer = !Objects.equals(currentOldResult, currentNewResult);
111
112 // uniqueness enforcement is happening here
113 if ((resultsDiffer || jumpDown) && !Objects.equals(previousOldResult, currentOldResult)) {
114 propagate(Direction.DELETE, group, currentOldResult, timestamp);
115 }
116 if ((resultsDiffer || jumpUp) && !Objects.equals(previousNewResult, currentNewResult)) {
117 propagate(Direction.INSERT, group, currentNewResult, timestamp);
118 }
119 }
120
121 /**
122 * Returns the aggregation architecture-specific accumulator at the specified timestamp for the given group.
123 */
124 protected abstract Accumulator getAccumulator(final Tuple group, final Timestamp timestamp);
125
126 protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) {
127 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entryMap = this.memory.get(group);
128 if (entryMap == null) {
129 return null;
130 } else {
131 CumulativeAggregate<Accumulator, AggregateResult> entry = null;
132 if (lower) {
133 final Entry<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> lowerEntry = entryMap
134 .lowerEntry(timestamp);
135 if (lowerEntry != null) {
136 entry = lowerEntry.getValue();
137 }
138 } else {
139 entry = entryMap.get(timestamp);
140 }
141 if (entry == null) {
142 return null;
143 } else {
144 return entry.result;
145 }
146 }
147 }
148
149 protected AggregateResult getResult(final Tuple group, final Timestamp timestamp, final boolean lower) {
150 final AggregateResult result = getResultRaw(group, timestamp, lower);
151 if (result == null) {
152 return NEUTRAL;
153 } else {
154 return result;
155 }
156 }
157
158 protected AggregateResult getResult(final Tuple group, final Timestamp timestamp) {
159 return getResult(group, timestamp, false);
160 }
161
162 protected void storeIfNotNeutral(final Tuple group, final Accumulator accumulator, final AggregateResult value,
163 final Timestamp timestamp) {
164 TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entryMap = this.memory.get(group);
165 if (operator.isNeutral(accumulator)) {
166 if (entryMap != null) {
167 entryMap.remove(timestamp);
168 if (entryMap.isEmpty()) {
169 this.memory.remove(group);
170 }
171 }
172 } else {
173 if (entryMap == null) {
174 entryMap = CollectionsFactory.createTreeMap();
175 this.memory.put(group, entryMap);
176 }
177 entryMap.put(timestamp, new CumulativeAggregate<>(accumulator, value));
178 }
179 }
180
181 @Override
182 public Tuple getAggregateTuple(final Tuple group) {
183 return tupleFromAggregateResult(group, getResult(group, Timestamp.ZERO));
184 }
185
186 @Override
187 public AggregateResult getAggregateResult(final Tuple group) {
188 return getResult(group, Timestamp.ZERO);
189 }
190
191 @Override
192 public Map<AggregateResult, Timeline<Timestamp>> getAggregateResultTimeline(final Tuple group) {
193 final TreeMap<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entryMap = this.memory.get(group);
194 if (entryMap == null) {
195 return Collections.emptyMap();
196 } else {
197 final Map<AggregateResult, Timeline<Timestamp>> result = CollectionsFactory.createMap();
198 for (final Entry<Timestamp, CumulativeAggregate<Accumulator, AggregateResult>> entry : entryMap
199 .descendingMap().entrySet()) {
200 result.put(entry.getValue().result, Timelines.createFrom(entry.getKey()));
201 }
202 return result;
203 }
204 }
205
206 @Override
207 public Map<Tuple, Timeline<Timestamp>> getAggregateTupleTimeline(final Tuple group) {
208 final Map<AggregateResult, Timeline<Timestamp>> resultTimelines = getAggregateResultTimeline(group);
209 return new GroupedMap<AggregateResult, Timeline<Timestamp>>(group, resultTimelines, this.runtimeContext);
210 }
211
212}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java
new file mode 100644
index 00000000..7bbf74ea
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.boundary;
11
12/**
13 * For objects that connect a RETE implementation to the underlying model.
14 *
15 * @author Gabor Bergmann
16 *
17 */
18public interface Disconnectable {
19
20 /**
21 * Disconnects this rete engine component from the underlying model. Disconnecting enables the garbage collection
22 * mechanisms to dispose of the rete network.
23 */
24 void disconnect();
25
26}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java
new file mode 100644
index 00000000..51f89b52
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java
@@ -0,0 +1,209 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.boundary;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.context.IInputKey;
16import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
22import tools.refinery.viatra.runtime.matchers.util.Direction;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.rete.matcher.ReteEngine;
25import tools.refinery.viatra.runtime.rete.network.Network;
26import tools.refinery.viatra.runtime.rete.network.Receiver;
27import tools.refinery.viatra.runtime.rete.network.ReteContainer;
28import tools.refinery.viatra.runtime.rete.network.StandardNode;
29import tools.refinery.viatra.runtime.rete.network.Supplier;
30import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
31import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
32import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
33import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
34import tools.refinery.viatra.runtime.rete.remote.Address;
35
36/**
37 * An input node representing an enumerable extensional input relation and receiving external updates.
38 *
39 * <p>
40 * Contains those tuples that are in the extensional relation identified by the input key, and also conform to the
41 * global seed (if any).
42 *
43 * @author Bergmann Gabor
44 *
45 */
46public class ExternalInputEnumeratorNode extends StandardNode
47 implements Disconnectable, Receiver, IQueryRuntimeContextListener {
48
49 private IQueryRuntimeContext context = null;
50 private IInputKey inputKey;
51 private Tuple globalSeed;
52 private InputConnector inputConnector;
53 private Network network;
54 private Address<? extends Receiver> myAddress;
55 private boolean parallelExecutionEnabled;
56 /**
57 * @since 1.6
58 */
59 protected final Mailbox mailbox;
60 private final IQueryBackendContext qBackendContext;
61
62 public ExternalInputEnumeratorNode(ReteContainer reteContainer) {
63 super(reteContainer);
64 myAddress = Address.of(this);
65 network = reteContainer.getNetwork();
66 inputConnector = network.getInputConnector();
67 qBackendContext = network.getEngine().getBackendContext();
68 mailbox = instantiateMailbox();
69 reteContainer.registerClearable(mailbox);
70 }
71
72 /**
73 * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own
74 * mailbox implementation.
75 *
76 * @return the mailbox
77 * @since 2.0
78 */
79 protected Mailbox instantiateMailbox() {
80 if (this.reteContainer.isTimelyEvaluation()) {
81 return new TimelyMailbox(this, this.reteContainer);
82 } else {
83 return new BehaviorChangingMailbox(this, this.reteContainer);
84 }
85 }
86
87 @Override
88 public Mailbox getMailbox() {
89 return this.mailbox;
90 }
91
92 public void connectThroughContext(ReteEngine engine, IInputKey inputKey, Tuple globalSeed) {
93 this.inputKey = inputKey;
94 this.globalSeed = globalSeed;
95 setTag(inputKey);
96
97 final IQueryRuntimeContext context = engine.getRuntimeContext();
98 if (!context.getMetaContext().isEnumerable(inputKey))
99 throw new IllegalArgumentException(this.getClass().getSimpleName()
100 + " only applicable for enumerable input keys; received instead " + inputKey);
101
102 this.context = context;
103 this.parallelExecutionEnabled = engine.isParallelExecutionEnabled();
104
105 engine.addDisconnectable(this);
106 context.addUpdateListener(inputKey, globalSeed, this);
107 }
108
109 @Override
110 public void disconnect() {
111 if (context != null) { // if connected
112 context.removeUpdateListener(inputKey, globalSeed, this);
113 context = null;
114 }
115 }
116
117 /**
118 * @since 2.2
119 */
120 protected Iterable<Tuple> getTuplesInternal() {
121 Iterable<Tuple> tuples = null;
122
123 if (context != null) { // if connected
124 if (globalSeed == null) {
125 tuples = context.enumerateTuples(inputKey, TupleMask.empty(inputKey.getArity()),
126 Tuples.staticArityFlatTupleOf());
127 } else {
128 final TupleMask mask = TupleMask.fromNonNullIndices(globalSeed);
129 tuples = context.enumerateTuples(inputKey, mask, mask.transform(globalSeed));
130 }
131 }
132
133 return tuples;
134 }
135
136 @Override
137 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
138 final Iterable<Tuple> tuples = getTuplesInternal();
139 if (tuples != null) {
140 for (final Tuple tuple : tuples) {
141 collector.add(tuple);
142 }
143 }
144 }
145
146 @Override
147 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
148 final Iterable<Tuple> tuples = getTuplesInternal();
149 if (tuples != null) {
150 for (final Tuple tuple : tuples) {
151 collector.put(tuple, Timestamp.INSERT_AT_ZERO_TIMELINE);
152 }
153 }
154 }
155
156 /* Update from runtime context */
157 @Override
158 public void update(IInputKey key, Tuple update, boolean isInsertion) {
159 if (parallelExecutionEnabled) {
160 // send back to myself as an official external update, and then propagate it transparently
161 network.sendExternalUpdate(myAddress, direction(isInsertion), update);
162 } else {
163 if (qBackendContext.areUpdatesDelayed()) {
164 // post the update into the mailbox of the node
165 mailbox.postMessage(direction(isInsertion), update, Timestamp.ZERO);
166 } else {
167 // just propagate the input
168 update(direction(isInsertion), update, Timestamp.ZERO);
169 }
170 // if the the update method is called from within a delayed execution,
171 // the following invocation will be a no-op
172 network.waitForReteTermination();
173 }
174 }
175
176 private static Direction direction(boolean isInsertion) {
177 return isInsertion ? Direction.INSERT : Direction.DELETE;
178 }
179
180 /* Self-addressed from network */
181 @Override
182 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
183 propagateUpdate(direction, updateElement, timestamp);
184 }
185
186 @Override
187 public void appendParent(Supplier supplier) {
188 throw new UnsupportedOperationException("Input nodes can't have parents");
189 }
190
191 @Override
192 public void removeParent(Supplier supplier) {
193 throw new UnsupportedOperationException("Input nodes can't have parents");
194 }
195
196 @Override
197 public Collection<Supplier> getParents() {
198 return Collections.emptySet();
199 }
200
201 public IInputKey getInputKey() {
202 return inputKey;
203 }
204
205 public Tuple getGlobalSeed() {
206 return globalSeed;
207 }
208
209}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java
new file mode 100644
index 00000000..57e06911
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java
@@ -0,0 +1,68 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.boundary;
10
11import tools.refinery.viatra.runtime.matchers.context.IInputKey;
12import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
15import tools.refinery.viatra.runtime.rete.matcher.ReteEngine;
16import tools.refinery.viatra.runtime.rete.network.ReteContainer;
17import tools.refinery.viatra.runtime.rete.single.FilterNode;
18
19/**
20 * A filter node representing a (stateless, typically non-enumerable) extensional input relation.
21 *
22 * <p> Contains those tuples of its parents, that (when transformed by a mask, if given) are present in the extensional relation identified by the input key.
23 *
24 * @author Bergmann Gabor
25 *
26 */
27public class ExternalInputStatelessFilterNode extends FilterNode implements Disconnectable {
28
29 IQueryRuntimeContext context = null;
30 IInputKey inputKey;
31 private InputConnector inputConnector;
32 private TupleMask mask;
33
34 public ExternalInputStatelessFilterNode(ReteContainer reteContainer, TupleMask mask) {
35 super(reteContainer);
36 this.mask = mask;
37 this.inputConnector = reteContainer.getNetwork().getInputConnector();
38 }
39
40 @Override
41 public boolean check(Tuple ps) {
42 if (mask != null)
43 ps = mask.transform(ps);
44 return context.containsTuple(inputKey, ps);
45 }
46
47
48 public void connectThroughContext(ReteEngine engine, IInputKey inputKey) {
49 this.inputKey = inputKey;
50 setTag(inputKey);
51
52 final IQueryRuntimeContext context = engine.getRuntimeContext();
53 if (!context.getMetaContext().isStateless(inputKey))
54 throw new IllegalArgumentException(
55 this.getClass().getSimpleName() +
56 " only applicable for stateless input keys; received instead " +
57 inputKey);
58
59 this.context = context;
60
61 engine.addDisconnectable(this);
62 }
63
64 @Override
65 public void disconnect() {
66 this.context = null;
67 }
68}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java
new file mode 100644
index 00000000..c044850f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java
@@ -0,0 +1,208 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.boundary;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14import java.util.stream.Stream;
15
16import tools.refinery.viatra.runtime.matchers.context.IInputKey;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.rete.network.Network;
21import tools.refinery.viatra.runtime.rete.network.Node;
22import tools.refinery.viatra.runtime.rete.recipes.InputFilterRecipe;
23import tools.refinery.viatra.runtime.rete.recipes.InputRecipe;
24import tools.refinery.viatra.runtime.rete.remote.Address;
25
26/**
27 * A class responsible for connecting input nodes to the runtime context.
28 *
29 * @author Bergmann Gabor
30 *
31 */
32public final class InputConnector {
33 Network network;
34
35 private Map<IInputKey, Map<Tuple, Address<ExternalInputEnumeratorNode>>> externalInputRoots = CollectionsFactory.createMap();
36
37// /*
38// * arity:1 used as simple entity constraints label is the object representing the type null label means all entities
39// * regardless of type (global supertype), if allowed
40// */
41// protected Map<Object, Address<? extends Tunnel>> unaryRoots = CollectionsFactory.getMap();
42// /*
43// * arity:3 (rel, from, to) used as VPM relation constraints null label means all relations regardless of type
44// * (global supertype)
45// */
46// protected Map<Object, Address<? extends Tunnel>> ternaryEdgeRoots = CollectionsFactory.getMap();
47// /*
48// * arity:2 (from, to) not used over VPM; can be used as EMF references for instance label is the object representing
49// * the type null label means all entities regardless of type if allowed (global supertype), if allowed
50// */
51// protected Map<Object, Address<? extends Tunnel>> binaryEdgeRoots = CollectionsFactory.getMap();
52//
53// protected Address<? extends Tunnel> containmentRoot = null;
54// protected Address<? extends Supplier> containmentTransitiveRoot = null;
55// protected Address<? extends Tunnel> instantiationRoot = null;
56// protected Address<? extends Supplier> instantiationTransitiveRoot = null;
57// protected Address<? extends Tunnel> generalizationRoot = null;
58// protected Address<? extends Supplier> generalizationTransitiveRoot = null;
59
60
61 public InputConnector(Network network) {
62 super();
63 this.network = network;
64 }
65
66
67 public Network getNetwork() {
68 return network;
69 }
70
71
72 /**
73 * Connects a given input filter node to the external input source.
74 */
75 public void connectInputFilter(InputFilterRecipe recipe, Node freshNode) {
76 final ExternalInputStatelessFilterNode inputNode = (ExternalInputStatelessFilterNode)freshNode;
77
78 IInputKey inputKey = (IInputKey) recipe.getInputKey();
79 inputNode.connectThroughContext(network.getEngine(), inputKey);
80 }
81
82
83 /**
84 * Connects a given input enumerator node to the external input source.
85 */
86 public void connectInput(InputRecipe recipe, Node freshNode) {
87 final ExternalInputEnumeratorNode inputNode = (ExternalInputEnumeratorNode)freshNode;
88
89 IInputKey inputKey = (IInputKey) recipe.getInputKey();
90 Tuple seed = nopSeed(inputKey); // no preseeding as of now
91 final Address<ExternalInputEnumeratorNode> freshAddress = Address.of(inputNode);
92 externalInputRoots.computeIfAbsent(inputKey, k -> CollectionsFactory.createMap()).put(seed, freshAddress);
93 inputNode.connectThroughContext(network.getEngine(), inputKey, seed);
94
95// final Address<Tunnel> freshAddress = Address.of((Tunnel)freshNode);
96// if (recipe instanceof TypeInputRecipe) {
97// final Object typeKey = ((TypeInputRecipe) recipe).getTypeKey();
98//
99// if (recipe instanceof UnaryInputRecipe) {
100// unaryRoots.put(typeKey, freshAddress);
101// new EntityFeeder(freshAddress, this, typeKey).feed();
102//// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) {
103//// Collection<? extends Object> subTypes = context.enumerateDirectUnarySubtypes(typeObject);
104////
105//// for (Object subType : subTypes) {
106//// Address<? extends Tunnel> subRoot = accessUnaryRoot(subType);
107//// network.connectRemoteNodes(subRoot, tn, true);
108//// }
109//// }
110// } else if (recipe instanceof BinaryInputRecipe) {
111// binaryEdgeRoots.put(typeKey, freshAddress);
112// externalInputRoots.put(rowKey, columnKey, freshAddress);
113// new ReferenceFeeder(freshAddress, this, typeKey).feed();
114// // if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) {
115// // Collection<? extends Object> subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject);
116// //
117// // for (Object subType : subTypes) {
118// // Address<? extends Tunnel> subRoot = accessTernaryEdgeRoot(subType);
119// // network.connectRemoteNodes(subRoot, tn, true);
120// // }
121// // }
122// }
123//
124//
125// }
126
127 }
128
129// /**
130// * fetches the entity Root node under specified label; returns null if it doesn't exist yet
131// */
132// public Address<? extends Tunnel> getUnaryRoot(Object label) {
133// return unaryRoots.get(label);
134// }
135//
136// public Collection<Address<? extends Tunnel>> getAllUnaryRoots() {
137// return unaryRoots.values();
138// }
139//
140// /**
141// * fetches the relation Root node under specified label; returns null if it doesn't exist yet
142// */
143// public Address<? extends Tunnel> getTernaryEdgeRoot(Object label) {
144// return ternaryEdgeRoots.get(label);
145// }
146//
147// public Collection<Address<? extends Tunnel>> getAllTernaryEdgeRoots() {
148// return ternaryEdgeRoots.values();
149// }
150//
151// /**
152// * fetches the reference Root node under specified label; returns null if it doesn't exist yet
153// */
154// public Address<? extends Tunnel> getBinaryEdgeRoot(Object label) {
155// return binaryEdgeRoots.get(label);
156// }
157//
158// public Collection<Address<? extends Tunnel>> getAllBinaryEdgeRoots() {
159// return binaryEdgeRoots.values();
160// }
161//
162//
163// public Address<? extends Tunnel> getContainmentRoot() {
164// return containmentRoot;
165// }
166//
167//
168// public Address<? extends Supplier> getContainmentTransitiveRoot() {
169// return containmentTransitiveRoot;
170// }
171//
172//
173// public Address<? extends Tunnel> getInstantiationRoot() {
174// return instantiationRoot;
175// }
176//
177//
178// public Address<? extends Supplier> getInstantiationTransitiveRoot() {
179// return instantiationTransitiveRoot;
180// }
181//
182//
183// public Address<? extends Tunnel> getGeneralizationRoot() {
184// return generalizationRoot;
185// }
186
187
188 public Stream<Address<ExternalInputEnumeratorNode>> getAllExternalInputNodes() {
189 return externalInputRoots.values().stream().flatMap(map -> map.values().stream());
190 }
191 public Collection<Address<ExternalInputEnumeratorNode>> getAllExternalInputNodesForKey(IInputKey inputKey) {
192 return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).values();
193 }
194 public Address<ExternalInputEnumeratorNode> getExternalInputNodeForKeyUnseeded(IInputKey inputKey) {
195 return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(nopSeed(inputKey));
196 }
197 public Address<ExternalInputEnumeratorNode> getExternalInputNode(IInputKey inputKey, Tuple seed) {
198 if (seed == null) seed = nopSeed(inputKey);
199 return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(seed);
200 }
201
202
203 Tuple nopSeed(IInputKey inputKey) {
204 return Tuples.flatTupleOf(new Object[inputKey.getArity()]);
205 }
206
207
208}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java
new file mode 100644
index 00000000..fe9c795e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java
@@ -0,0 +1,551 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.boundary;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
17import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
18import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.Direction;
22import tools.refinery.viatra.runtime.rete.matcher.ReteEngine;
23import tools.refinery.viatra.runtime.rete.network.Network;
24import tools.refinery.viatra.runtime.rete.network.ProductionNode;
25import tools.refinery.viatra.runtime.rete.network.Receiver;
26import tools.refinery.viatra.runtime.rete.network.ReteContainer;
27import tools.refinery.viatra.runtime.rete.network.Supplier;
28import tools.refinery.viatra.runtime.rete.remote.Address;
29import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery;
30import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
31
32/**
33 * Responsible for the storage, maintenance and communication of the nodes of the network that are accessible form the
34 * outside for various reasons.
35 *
36 * @author Gabor Bergmann
37 *
38 * <p> TODO: should eventually be merged into {@link InputConnector} and deleted
39 *
40 */
41public class ReteBoundary /*implements IPatternMatcherRuntimeContextListener*/ {
42
43 protected ReteEngine engine;
44 protected Network network;
45 protected ReteContainer headContainer;
46
47 public ReteContainer getHeadContainer() {
48 return headContainer;
49 }
50
51 protected final InputConnector inputConnector;
52
53
54 protected Map<SubPlan, Address<? extends Supplier>> subplanToAddressMapping;
55
56
57 /**
58 * SubPlans of parent nodes that have the key node as their child. For RETE --> SubPlan traceability, mainly at production
59 * nodes.
60 */
61 protected Map<Address<? extends Receiver>, Set<SubPlan>> parentPlansOfReceiver;
62
63 /**
64 * Prerequisite: engine has its network and framework fields initialized
65 */
66 public ReteBoundary(ReteEngine engine) {
67 super();
68 this.engine = engine;
69 this.network = engine.getReteNet();
70 this.headContainer = network.getHeadContainer();
71 inputConnector = network.getInputConnector();
72
73 this.parentPlansOfReceiver = CollectionsFactory.createMap();
74
75 // productionsScoped = new HashMap<GTPattern, Map<Map<Integer,Scope>,Address<? extends Production>>>();
76 subplanToAddressMapping = CollectionsFactory.createMap();
77
78 }
79
80 public Collection<? extends RecipeTraceInfo> getAllProductionNodes() {
81 return engine.getCompiler().getCachedCompiledQueries().values();
82 }
83
84// /**
85// * accesses the entity Root node under specified label; creates the node if it doesn't exist yet
86// */
87// public Address<? extends Tunnel> accessUnaryRoot(Object typeObject) {
88// Address<? extends Tunnel> tn;
89// tn = unaryRoots.get(typeObject);
90// if (tn == null) {
91// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(1, typeObject);
92// unaryRoots.put(typeObject, tn);
93//
94// new EntityFeeder(tn, context, network, this, typeObject).feed();
95//
96// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) {
97// Collection<? extends Object> subTypes = context.enumerateDirectUnarySubtypes(typeObject);
98//
99// for (Object subType : subTypes) {
100// Address<? extends Tunnel> subRoot = accessUnaryRoot(subType);
101// network.connectRemoteNodes(subRoot, tn, true);
102// }
103// }
104//
105// }
106// return tn;
107// }
108//
109// /**
110// * accesses the relation Root node under specified label; creates the node if it doesn't exist yet
111// */
112// public Address<? extends Tunnel> accessTernaryEdgeRoot(Object typeObject) {
113// Address<? extends Tunnel> tn;
114// tn = ternaryEdgeRoots.get(typeObject);
115// if (tn == null) {
116// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(3, typeObject);
117// ternaryEdgeRoots.put(typeObject, tn);
118//
119// new RelationFeeder(tn, context, network, this, typeObject).feed();
120//
121// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) {
122// Collection<? extends Object> subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject);
123//
124// for (Object subType : subTypes) {
125// Address<? extends Tunnel> subRoot = accessTernaryEdgeRoot(subType);
126// network.connectRemoteNodes(subRoot, tn, true);
127// }
128// }
129// }
130// return tn;
131// }
132//
133// /**
134// * accesses the reference Root node under specified label; creates the node if it doesn't exist yet
135// */
136// public Address<? extends Tunnel> accessBinaryEdgeRoot(Object typeObject) {
137// Address<? extends Tunnel> tn;
138// tn = binaryEdgeRoots.get(typeObject);
139// if (tn == null) {
140// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(2, typeObject);
141// binaryEdgeRoots.put(typeObject, tn);
142//
143// new ReferenceFeeder(tn, context, network, this, typeObject).feed();
144//
145// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) {
146// Collection<? extends Object> subTypes = context.enumerateDirectBinaryEdgeSubtypes(typeObject);
147//
148// for (Object subType : subTypes) {
149// Address<? extends Tunnel> subRoot = accessBinaryEdgeRoot(subType);
150// network.connectRemoteNodes(subRoot, tn, true);
151// }
152// }
153// }
154// return tn;
155// }
156//
157// /**
158// * accesses the special direct containment relation Root node; creates the node if it doesn't exist yet
159// */
160// public Address<? extends Tunnel> accessContainmentRoot() {
161// if (containmentRoot == null) {
162// // containment: relation quasi-type
163// containmentRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$containment");
164//
165// new ContainmentFeeder(containmentRoot, context, network, this).feed();
166// }
167// return containmentRoot;
168// }
169//
170// /**
171// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet
172// */
173// public Address<? extends Supplier> accessContainmentTransitiveRoot() {
174// if (containmentTransitiveRoot == null) {
175// // transitive containment: derived
176// Address<? extends Tunnel> containmentTransitiveRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(
177// 2, "$containmentTransitive");
178// network.connectRemoteNodes(accessContainmentRoot(), containmentTransitiveRoot, true);
179//
180// final int[] actLI = { 1 };
181// final int arcLIw = 2;
182// final int[] actRI = { 0 };
183// final int arcRIw = 2;
184// Address<? extends IterableIndexer> jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer(
185// accessContainmentRoot(), new TupleMask(actLI, arcLIw));
186// Address<? extends IterableIndexer> jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer(
187// containmentTransitiveRoot, new TupleMask(actRI, arcRIw));
188//
189// final int[] actRIcomp = { 1 };
190// final int arcRIwcomp = 2;
191// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp);
192//
193// Address<? extends Supplier> andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot,
194// complementerMask);
195//
196// final int[] mask = { 0, 2 };
197// final int maskw = 3;
198// Address<? extends Supplier> tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw));
199// network.connectRemoteNodes(tr, containmentTransitiveRoot, true);
200//
201// this.containmentTransitiveRoot = containmentTransitiveRoot; // cast
202// // back
203// // to
204// // Supplier
205// }
206// return containmentTransitiveRoot;
207// }
208//
209// /**
210// * accesses the special instantiation relation Root node; creates the node if it doesn't exist yet
211// */
212// public Address<? extends Tunnel> accessInstantiationRoot() {
213// if (instantiationRoot == null) {
214// // instantiation: relation quasi-type
215// instantiationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$instantiation");
216//
217// new InstantiationFeeder(instantiationRoot, context, network, this).feed();
218// }
219// return instantiationRoot;
220// }
221//
222// /**
223// * accesses the special transitive instantiation relation Root node; creates the node if it doesn't exist yet
224// * InstantiationTransitive = Instantiation o (Generalization)^*
225// */
226// public Address<? extends Supplier> accessInstantiationTransitiveRoot() {
227// if (instantiationTransitiveRoot == null) {
228// // transitive instantiation: derived
229// Address<? extends Tunnel> instantiationTransitiveRoot = headContainer.getProvisioner()
230// .newUniquenessEnforcerNode(2, "$instantiationTransitive");
231// network.connectRemoteNodes(accessInstantiationRoot(), instantiationTransitiveRoot, true);
232//
233// final int[] actLI = { 1 };
234// final int arcLIw = 2;
235// final int[] actRI = { 0 };
236// final int arcRIw = 2;
237// Address<? extends IterableIndexer> jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer(
238// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw));
239// Address<? extends Indexer> jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer(
240// instantiationTransitiveRoot, new TupleMask(actRI, arcRIw));
241//
242// final int[] actRIcomp = { 1 };
243// final int arcRIwcomp = 2;
244// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp);
245//
246// Address<? extends Supplier> andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot,
247// complementerMask);
248//
249// final int[] mask = { 0, 2 };
250// final int maskw = 3;
251// Address<? extends Supplier> tr = headContainer.getProvisioner().accessTrimmerNode(andCT,
252// new TupleMask(mask, maskw));
253// network.connectRemoteNodes(tr, instantiationTransitiveRoot, true);
254//
255// this.instantiationTransitiveRoot = instantiationTransitiveRoot; // cast
256// // back
257// // to
258// // Supplier
259// }
260// return instantiationTransitiveRoot;
261// }
262//
263// /**
264// * accesses the special generalization relation Root node; creates the node if it doesn't exist yet
265// */
266// public Address<? extends Tunnel> accessGeneralizationRoot() {
267// if (generalizationRoot == null) {
268// // generalization: relation quasi-type
269// generalizationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$generalization");
270//
271// new GeneralizationFeeder(generalizationRoot, context, network, this).feed();
272// }
273// return generalizationRoot;
274// }
275//
276// /**
277// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet
278// */
279// public Address<? extends Supplier> accessGeneralizationTransitiveRoot() {
280// if (generalizationTransitiveRoot == null) {
281// // transitive generalization: derived
282// Address<? extends Tunnel> generalizationTransitiveRoot = headContainer.getProvisioner()
283// .newUniquenessEnforcerNode(2, "$generalizationTransitive");
284// network.connectRemoteNodes(accessGeneralizationRoot(), generalizationTransitiveRoot, true);
285//
286// final int[] actLI = { 1 };
287// final int arcLIw = 2;
288// final int[] actRI = { 0 };
289// final int arcRIw = 2;
290// Address<? extends IterableIndexer> jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer(
291// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw));
292// Address<? extends Indexer> jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer(
293// generalizationTransitiveRoot, new TupleMask(actRI, arcRIw));
294//
295// final int[] actRIcomp = { 1 };
296// final int arcRIwcomp = 2;
297// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp);
298//
299// Address<? extends Supplier> andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot,
300// complementerMask);
301//
302// final int[] mask = { 0, 2 };
303// final int maskw = 3;
304// Address<? extends Supplier> tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw));
305// network.connectRemoteNodes(tr, generalizationTransitiveRoot, true);
306//
307// this.generalizationTransitiveRoot = generalizationTransitiveRoot; // cast
308// // back
309// // to
310// // Supplier
311// }
312// return generalizationTransitiveRoot;
313// }
314
315 // /**
316 // * Registers and publishes a supplier under specified label.
317 // */
318 // public void publishSupplier(Supplier s, Object label)
319 // {
320 // publishedSuppliers.put(label, s);
321 // }
322 //
323 // /**
324 // * fetches the production node under specified label;
325 // * returns null if it doesn't exist yet
326 // */
327 // public Production getProductionNode(Object label)
328 // {
329 // return productions.get(label);
330 // }
331 //
332 // /**
333 // * fetches the published supplier under specified label;
334 // * returns null if it doesn't exist yet
335 // */
336 // public Supplier getPublishedSupplier(Object label)
337 // {
338 // return publishedSuppliers.get(label);
339 // }
340
341 /**
342 * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet
343 * @throws ViatraQueryRuntimeException
344 */
345 public synchronized RecipeTraceInfo accessProductionTrace(PQuery query)
346 {
347 final CompiledQuery compiled = engine.getCompiler().getCompiledForm(query);
348 return compiled;
349// RecipeTraceInfo pn;
350// pn = queryPlans.get(query);
351// if (pn == null) {
352// pn = construct(query);
353// TODO handle recursion by reinterpret-RecipeTrace
354// queryPlans.put(query, pn);
355// if (pn == null) {
356// String[] args = { query.toString() };
357// throw new RetePatternBuildException("Unsuccessful planning of RETE construction recipe for query {1}",
358// args, "Could not create RETE recipe plan.", query);
359// }
360// }
361// return pn;
362 }
363 /**
364 * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet
365 * @throws ViatraQueryRuntimeException
366 */
367 public synchronized Address<? extends ProductionNode> accessProductionNode(PQuery query) {
368 final RecipeTraceInfo productionTrace = accessProductionTrace(query);
369 return (Address<? extends ProductionNode>) headContainer.getProvisioner().getOrCreateNodeByRecipe(productionTrace);
370 }
371
372// /**
373// * creates the production node for the specified pattern Contract: only call from the builder (through Buildable)
374// * responsible for building this pattern
375// *
376// * @throws PatternMatcherCompileTimeException
377// * if production node is already created
378// */
379// public synchronized Address<? extends Production> createProductionInternal(PQuery gtPattern)
380// throws QueryPlannerException {
381// if (queryPlans.containsKey(gtPattern)) {
382// String[] args = { gtPattern.toString() };
383// throw new RetePatternBuildException("Multiple creation attempts of production node for {1}", args,
384// "Duplicate RETE production node.", gtPattern);
385// }
386//
387// Map<String, Integer> posMapping = engine.getBuilder().getPosMapping(gtPattern);
388// Address<? extends Production> pn = headContainer.getProvisioner().newProductionNode(posMapping, gtPattern);
389// queryPlans.put(gtPattern, pn);
390// context.reportPatternDependency(gtPattern);
391//
392// return pn;
393// }
394
395 // /**
396 // * accesses the production node for specified pattern and scope map; creates the node if
397 // * it doesn't exist yet
398 // */
399 // public synchronized Address<? extends Production> accessProductionScoped(
400 // GTPattern gtPattern, Map<Integer, Scope> additionalScopeMap) throws PatternMatcherCompileTimeException {
401 // if (additionalScopeMap.isEmpty()) return accessProduction(gtPattern);
402 //
403 // Address<? extends Production> pn;
404 //
405 // Map<Map<Integer, Scope>, Address<? extends Production>> scopes = productionsScoped.get(gtPattern);
406 // if (scopes == null) {
407 // scopes = new HashMap<Map<Integer, Scope>, Address<? extends Production>>();
408 // productionsScoped.put(gtPattern, scopes);
409 // }
410 //
411 // pn = scopes.get(additionalScopeMap);
412 // if (pn == null) {
413 // Address<? extends Production> unscopedProduction = accessProduction(gtPattern);
414 //
415 // HashMap<Object, Integer> posMapping = headContainer.resolveLocal(unscopedProduction).getPosMapping();
416 // pn = headContainer.getLibrary().newProductionNode(posMapping);
417 // scopes.put(additionalScopeMap, pn);
418 //
419 // constructScoper(unscopedProduction, additionalScopeMap, pn);
420 // }
421 // return pn;
422 // }
423
424 // protected void constructScoper(
425 // Address<? extends Production> unscopedProduction,
426 // Map<Integer, Scope> additionalScopeMap,
427 // Address<? extends Production> production)
428 // throws PatternMatcherCompileTimeException {
429 // engine.reteNet.waitForReteTermination();
430 // engine.builder.constructScoper(unscopedProduction, additionalScopeMap, production);
431 // }
432
433 // /**
434 // * Invalidates the subnet constructed for the recognition of a given
435 // pattern.
436 // * The pattern matcher will have to be rebuilt.
437 // * @param gtPattern the pattern whose matcher subnet should be invalidated
438 // */
439 // public void invalidatePattern(GTPattern gtPattern) {
440 // Production production = null;
441 // try {
442 // production = accessProduction(gtPattern);
443 // } catch (PatternMatcherCompileTimeException e) {
444 // // this should not occur here, since we already have a production node
445 // e.printStackTrace();
446 // }
447 //
448 // production.tearOff();
449 // //production.setDirty(true);
450 // }
451
452 // updaters for change notification
453 // if the corresponding rete input isn't created yet, call is ignored
454
455 private static Direction direction(boolean isInsertion) {
456 return isInsertion ? Direction.INSERT : Direction.DELETE;
457 }
458
459// @Override
460// public void updateUnary(boolean isInsertion, Object entity, Object typeObject) {
461// Address<? extends Tunnel> root = inputConnector.getUnaryRoot(typeObject);
462// if (root != null) {
463// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(entity)));
464// if (!engine.isParallelExecutionEnabled())
465// network.waitForReteTermination();
466// }
467// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) {
468// for (Object superType : context.enumerateDirectUnarySupertypes(typeObject)) {
469// updateUnary(isInsertion, entity, superType);
470// }
471// }
472// }
473//
474// @Override
475// public void updateTernaryEdge(boolean isInsertion, Object relation, Object from, Object to, Object typeObject) {
476// Address<? extends Tunnel> root = inputConnector.getTernaryEdgeRoot(typeObject);
477// if (root != null) {
478// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(relation), inputConnector.wrapElement(from),
479// inputConnector.wrapElement(to)));
480// if (!engine.isParallelExecutionEnabled())
481// network.waitForReteTermination();
482// }
483// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) {
484// for (Object superType : context.enumerateDirectTernaryEdgeSupertypes(typeObject)) {
485// updateTernaryEdge(isInsertion, relation, from, to, superType);
486// }
487// }
488// }
489// @Override
490// public void updateBinaryEdge(boolean isInsertion, Object from, Object to, Object typeObject) {
491// Address<? extends Tunnel> root = inputConnector.getBinaryEdgeRoot(typeObject);
492// if (root != null) {
493// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(from), inputConnector.wrapElement(to)));
494// if (!engine.isParallelExecutionEnabled())
495// network.waitForReteTermination();
496// }
497// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) {
498// for (Object superType : context.enumerateDirectBinaryEdgeSupertypes(typeObject)) {
499// updateBinaryEdge(isInsertion, from, to, superType);
500// }
501// }
502// }
503//
504// @Override
505// public void updateContainment(boolean isInsertion, Object container, Object element) {
506// final Address<? extends Tunnel> containmentRoot = inputConnector.getContainmentRoot();
507// if (containmentRoot != null) {
508// network.sendExternalUpdate(containmentRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(container),
509// inputConnector.wrapElement(element)));
510// if (!engine.isParallelExecutionEnabled())
511// network.waitForReteTermination();
512// }
513// }
514//
515// @Override
516// public void updateInstantiation(boolean isInsertion, Object parent, Object child) {
517// final Address<? extends Tunnel> instantiationRoot = inputConnector.getInstantiationRoot();
518// if (instantiationRoot != null) {
519// network.sendExternalUpdate(instantiationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent),
520// inputConnector.wrapElement(child)));
521// if (!engine.isParallelExecutionEnabled())
522// network.waitForReteTermination();
523// }
524// }
525//
526// @Override
527// public void updateGeneralization(boolean isInsertion, Object parent, Object child) {
528// final Address<? extends Tunnel> generalizationRoot = inputConnector.getGeneralizationRoot();
529// if (generalizationRoot != null) {
530// network.sendExternalUpdate(generalizationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent),
531// inputConnector.wrapElement(child)));
532// if (!engine.isParallelExecutionEnabled())
533// network.waitForReteTermination();
534// }
535// }
536
537 // no wrapping needed!
538 public void notifyEvaluator(Address<? extends Receiver> receiver, Tuple tuple) {
539 network.sendExternalUpdate(receiver, Direction.INSERT, tuple);
540 if (!engine.isParallelExecutionEnabled())
541 network.waitForReteTermination();
542 }
543
544 public void mapPlanToAddress(SubPlan plan, Address<? extends Supplier> handle) {
545 subplanToAddressMapping.put(plan, handle);
546 }
547
548 public Address<? extends Supplier> getAddress(SubPlan plan) {
549 return subplanToAddressMapping.get(plan);
550 }
551}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java
new file mode 100644
index 00000000..bd1b219e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction;
11
12import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
13
14/**
15 * A problem has occurred during the construction of the RETE net.
16 *
17 * @author Gabor Bergmann
18 *
19 */
20public class RetePatternBuildException extends QueryProcessingException {
21
22 private static final long serialVersionUID = 6966585498204577548L;
23
24 /**
25 * @param message
26 * The template of the exception message
27 * @param context
28 * The data elements to be used to instantiate the template. Can be null if no context parameter is
29 * defined
30 * @param patternDescription
31 * the PatternDescription where the exception occurred
32 */
33 public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription) {
34 super(message, context, shortMessage, patternDescription);
35 }
36
37 /**
38 * @param message
39 * The template of the exception message
40 * @param context
41 * The data elements to be used to instantiate the template. Can be null if no context parameter is
42 * defined
43 * @param patternDescription
44 * the PatternDescription where the exception occurred
45 */
46 public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription,
47 Throwable cause) {
48 super(message, context, shortMessage, patternDescription, cause);
49 }
50}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java
new file mode 100644
index 00000000..bd22e1a0
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction.basiclinear;
11
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.Set;
15
16import org.apache.log4j.Logger;
17import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
18import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
19import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
20import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy;
21import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
22import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory;
23import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper;
24import tools.refinery.viatra.runtime.matchers.planning.operations.PApply;
25import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
26import tools.refinery.viatra.runtime.matchers.planning.operations.PStart;
27import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint;
28import tools.refinery.viatra.runtime.matchers.psystem.PBody;
29import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
30import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
31import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
32import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality;
33import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
34import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation;
35import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
36import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
37import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException;
38
39/**
40 * Basic layout that builds a linear RETE net based on a heuristic ordering of constraints.
41 *
42 * @author Gabor Bergmann
43 *
44 */
45public class BasicLinearLayout implements IQueryPlannerStrategy {
46
47 //SubPlanProcessor planProcessor = new SubPlanProcessor();
48
49 private IQueryBackendHintProvider hintProvider;
50 private IQueryBackendContext bContext;
51 /**
52 * @param bContext
53 * @since 1.5
54 */
55 public BasicLinearLayout(IQueryBackendContext bContext) {
56 this.bContext = bContext;
57 this.hintProvider = bContext.getHintProvider();
58 }
59
60 @Override
61 public SubPlan plan(final PBody pSystem, Logger logger, IQueryMetaContext context) {
62 SubPlanFactory planFactory = new SubPlanFactory(pSystem);
63 PQuery query = pSystem.getPattern();
64 //planProcessor.setCompiler(compiler);
65 try {
66 logger.debug(String.format(
67 "%s: patternbody build started for %s",
68 getClass().getSimpleName(),
69 query.getFullyQualifiedName()));
70
71 // STARTING THE LINE
72 SubPlan plan = planFactory.createSubPlan(new PStart());
73
74 Set<PConstraint> pQueue = CollectionsFactory.createSet(pSystem.getConstraints());
75
76 // MAIN LOOP
77 while (!pQueue.isEmpty()) {
78 PConstraint pConstraint = Collections.min(pQueue,
79 new OrderingHeuristics(plan, context)); // pQueue.iterator().next();
80 pQueue.remove(pConstraint);
81
82 // if we have no better option than an unready deferred constraint, raise error
83 if (pConstraint instanceof DeferredPConstraint) {
84 final DeferredPConstraint deferred = (DeferredPConstraint) pConstraint;
85 if (!deferred.isReadyAt(plan, context)) {
86 raiseForeverDeferredError(deferred, plan, context);
87 }
88 }
89 // TODO integrate the check above in SubPlan / POperation??
90
91 // replace incumbent plan with its child
92 plan = planFactory.createSubPlan(new PApply(pConstraint), plan);
93 }
94
95 // PROJECT TO PARAMETERS
96 SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), plan);
97
98 // FINAL CHECK, whether all exported variables are present + all constraint satisfied
99 BuildHelper.finalCheck(pSystem, finalPlan, context);
100 // TODO integrate the check above in SubPlan / POperation
101
102 logger.debug(String.format(
103 "%s: patternbody query plan concluded for %s as: %s",
104 getClass().getSimpleName(),
105 query.getFullyQualifiedName(),
106 finalPlan.toLongString()));
107
108 return finalPlan;
109
110 } catch (RetePatternBuildException ex) {
111 ex.setPatternDescription(query);
112 throw ex;
113 }
114 }
115
116 /**
117 * Called when the constraint is not ready, but cannot be deferred further.
118 *
119 * @param plan
120 * @throws RetePatternBuildException
121 * to indicate the error in detail.
122 */
123 private void raiseForeverDeferredError(DeferredPConstraint constraint, SubPlan plan, IQueryMetaContext context) {
124 if (constraint instanceof Equality) {
125 raiseForeverDeferredError((Equality)constraint, plan, context);
126 } else if (constraint instanceof ExportedParameter) {
127 raiseForeverDeferredError((ExportedParameter)constraint, plan, context);
128 } else if (constraint instanceof ExpressionEvaluation) {
129 raiseForeverDeferredError((ExpressionEvaluation)constraint, plan, context);
130 } else if (constraint instanceof VariableDeferredPConstraint) {
131 raiseForeverDeferredError(constraint, plan, context);
132 }
133 }
134
135 private void raiseForeverDeferredError(Equality constraint, SubPlan plan, IQueryMetaContext context) {
136 String[] args = { constraint.getWho().toString(), constraint.getWithWhom().toString() };
137 String msg = "Cannot express equality of variables {1} and {2} if neither of them is deducable.";
138 String shortMsg = "Equality between undeducible variables.";
139 throw new RetePatternBuildException(msg, args, shortMsg, null);
140 }
141 private void raiseForeverDeferredError(ExportedParameter constraint, SubPlan plan, IQueryMetaContext context) {
142 String[] args = { constraint.getParameterName() };
143 String msg = "Pattern Graph Search terminated incompletely: "
144 + "exported pattern variable {1} could not be determined based on the pattern constraints. "
145 + "HINT: certain constructs (e.g. negative patterns or check expressions) cannot output symbolic parameters.";
146 String shortMsg = "Could not deduce value of parameter";
147 throw new RetePatternBuildException(msg, args, shortMsg, null);
148 }
149 private void raiseForeverDeferredError(ExpressionEvaluation constraint, SubPlan plan, IQueryMetaContext context) {
150 if (constraint.checkTypeSafety(plan, context) == null) {
151 raiseForeverDeferredError(constraint, plan);
152 } else {
153 String[] args = { toString(), constraint.checkTypeSafety(plan, context).toString() };
154 String msg = "The checking of pattern constraint {1} cannot be deferred further, but variable {2} is still not type safe. "
155 + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible.";
156 String shortMsg = "Could not check all constraints due to undeducible type restrictions";
157 throw new RetePatternBuildException(msg, args, shortMsg, null);
158 }
159 }
160 private void raiseForeverDeferredError(VariableDeferredPConstraint constraint, SubPlan plan) {
161 Set<PVariable> missing = CollectionsFactory.createSet(constraint.getDeferringVariables());//new HashSet<PVariable>(getDeferringVariables());
162 missing.removeAll(plan.getVisibleVariables());
163 String[] args = { toString(), Arrays.toString(missing.toArray()) };
164 String msg = "The checking of pattern constraint {1} requires the values of variables {2}, but it cannot be deferred further. "
165 + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible.";
166 String shortMsg = "Could not check all constraints due to undeducible variables";
167 throw new RetePatternBuildException(msg, args, shortMsg, null);
168 }
169
170
171}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java
new file mode 100644
index 00000000..2b4e8890
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java
@@ -0,0 +1,90 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction.basiclinear;
11
12import java.util.Comparator;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
16import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
17import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint;
18import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent;
24
25/**
26 * @author Gabor Bergmann
27 *
28 */
29public class OrderingHeuristics implements Comparator<PConstraint> {
30 private SubPlan plan;
31 private IQueryMetaContext context;
32
33 public OrderingHeuristics(SubPlan plan, IQueryMetaContext context) {
34 super();
35 this.plan = plan;
36 this.context = context;
37 }
38
39 @Override
40 public int compare(PConstraint o1, PConstraint o2) {
41 return new OrderingCompareAgent<PConstraint>(o1, o2) {
42 @Override
43 protected void doCompare() {
44 boolean temp = consider(preferTrue(isConstant(a), isConstant(b)))
45 && consider(preferTrue(isReady(a), isReady(b)));
46 if (!temp)
47 return;
48
49 Set<PVariable> bound1 = boundVariables(a);
50 Set<PVariable> bound2 = boundVariables(b);
51 swallowBoolean(temp && consider(preferTrue(isBound(a, bound1), isBound(b, bound2)))
52 && consider(preferMore(degreeBound(a, bound1), degreeBound(b, bound2)))
53 && consider(preferLess(degreeFree(a, bound1), degreeFree(b, bound2)))
54
55 // tie breaking
56 && consider(preferLess(a.getMonotonousID(), b.getMonotonousID())) // this is hopefully deterministic
57 && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b))));
58 }
59 }.compare();
60 }
61
62 boolean isConstant(PConstraint o) {
63 return (o instanceof ConstantValue);
64 }
65
66 boolean isReady(PConstraint o) {
67 return (o instanceof EnumerablePConstraint)
68 || (o instanceof DeferredPConstraint && ((DeferredPConstraint) o)
69 .isReadyAt(plan, context));
70 }
71
72 Set<PVariable> boundVariables(PConstraint o) {
73 Set<PVariable> boundVariables = CollectionsFactory.createSet(o.getAffectedVariables());
74 boundVariables.retainAll(plan.getVisibleVariables());
75 return boundVariables;
76 }
77
78 boolean isBound(PConstraint o, Set<PVariable> boundVariables) {
79 return boundVariables.size() == o.getAffectedVariables().size();
80 }
81
82 int degreeBound(PConstraint o, Set<PVariable> boundVariables) {
83 return boundVariables.size();
84 }
85
86 int degreeFree(PConstraint o, Set<PVariable> boundVariables) {
87 return o.getAffectedVariables().size() - boundVariables.size();
88 }
89
90}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java
new file mode 100644
index 00000000..da2fb432
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java
@@ -0,0 +1,390 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.construction.plancompiler;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.LinkedHashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Map;
19import java.util.Map.Entry;
20import java.util.Set;
21import java.util.SortedSet;
22import java.util.TreeSet;
23
24import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
25import tools.refinery.viatra.runtime.matchers.context.IInputKey;
26import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
27import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
28import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
29import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper;
30import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
31import tools.refinery.viatra.runtime.matchers.psystem.PBody;
32import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
33import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
34import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
35import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
36import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
37import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
38import tools.refinery.viatra.runtime.rete.recipes.EqualityFilterRecipe;
39import tools.refinery.viatra.runtime.rete.recipes.IndexerBasedAggregatorRecipe;
40import tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe;
41import tools.refinery.viatra.runtime.rete.recipes.JoinRecipe;
42import tools.refinery.viatra.runtime.rete.recipes.Mask;
43import tools.refinery.viatra.runtime.rete.recipes.MonotonicityInfo;
44import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe;
45import tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe;
46import tools.refinery.viatra.runtime.rete.recipes.RecipesFactory;
47import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
48import tools.refinery.viatra.runtime.rete.recipes.SingleColumnAggregatorRecipe;
49import tools.refinery.viatra.runtime.rete.recipes.TrimmerRecipe;
50import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper;
51import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery;
52import tools.refinery.viatra.runtime.rete.traceability.CompiledSubPlan;
53import tools.refinery.viatra.runtime.rete.traceability.PlanningTrace;
54import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
55import tools.refinery.viatra.runtime.rete.util.ReteHintOptions;
56
57/**
58 * @author Bergmann Gabor
59 *
60 */
61public class CompilerHelper {
62
63 private CompilerHelper() {/*Utility class constructor*/}
64
65 static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE;
66
67 /**
68 * Makes sure that all variables in the tuple are different so that it can be used as {@link CompiledSubPlan}. If a
69 * variable occurs multiple times, equality checks are applied and then the results are trimmed so that duplicates
70 * are hidden. If no manipulation is necessary, the original trace is returned.
71 *
72 * <p>
73 * to be used whenever a constraint introduces new variables.
74 */
75 public static PlanningTrace checkAndTrimEqualVariables(SubPlan plan, final PlanningTrace coreTrace) {
76 // are variables in the constraint all different?
77 final List<PVariable> coreVariablesTuple = coreTrace.getVariablesTuple();
78 final int constraintArity = coreVariablesTuple.size();
79 final int distinctVariables = coreTrace.getPosMapping().size();
80 if (constraintArity == distinctVariables) {
81 // all variables occur exactly once in tuple
82 return coreTrace;
83 } else { // apply equality checks and trim
84
85 // find the positions in the tuple for each variable
86 Map<PVariable, SortedSet<Integer>> posMultimap = new HashMap<PVariable, SortedSet<Integer>>();
87 List<PVariable> trimmedVariablesTuple = new ArrayList<PVariable>(distinctVariables);
88 int[] trimIndices = new int[distinctVariables];
89 for (int i = 0; i < constraintArity; ++i) {
90 final PVariable variable = coreVariablesTuple.get(i);
91 SortedSet<Integer> indexSet = posMultimap.get(variable);
92 if (indexSet == null) { // first occurrence of variable
93 indexSet = new TreeSet<Integer>();
94 posMultimap.put(variable, indexSet);
95
96 // this is the first occurrence, set up trimming
97 trimIndices[trimmedVariablesTuple.size()] = i;
98 trimmedVariablesTuple.add(variable);
99 }
100 indexSet.add(i);
101 }
102
103 // construct equality checks for each variable occurring multiple times
104 PlanningTrace lastTrace = coreTrace;
105 for (Entry<PVariable, SortedSet<Integer>> entry : posMultimap.entrySet()) {
106 if (entry.getValue().size() > 1) {
107 EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe();
108 equalityFilterRecipe.setParent(lastTrace.getRecipe());
109 equalityFilterRecipe.getIndices().addAll(entry.getValue());
110 lastTrace = new PlanningTrace(plan, coreVariablesTuple, equalityFilterRecipe, lastTrace);
111 }
112 }
113
114 // trim so that each variable occurs only once
115 TrimmerRecipe trimmerRecipe = FACTORY.createTrimmerRecipe();
116 trimmerRecipe.setParent(lastTrace.getRecipe());
117 trimmerRecipe.setMask(tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper
118 .mask(constraintArity, trimIndices));
119 return new PlanningTrace(plan, trimmedVariablesTuple, trimmerRecipe, lastTrace);
120 }
121 }
122
123 /**
124 * Extracts the variable list representation of the variables tuple.
125 */
126 public static List<PVariable> convertVariablesTuple(EnumerablePConstraint constraint) {
127 return convertVariablesTuple(constraint.getVariablesTuple());
128 }
129
130 /**
131 * Extracts the variable list representation of the variables tuple.
132 */
133 public static List<PVariable> convertVariablesTuple(Tuple variablesTuple) {
134 List<PVariable> result = new ArrayList<PVariable>();
135 for (Object o : variablesTuple.getElements())
136 result.add((PVariable) o);
137 return result;
138 }
139
140 /**
141 * Returns a compiled indexer trace according to a mask
142 */
143 public static RecipeTraceInfo makeIndexerTrace(SubPlan planToCompile, PlanningTrace parentTrace, TupleMask mask) {
144 final ReteNodeRecipe parentRecipe = parentTrace.getRecipe();
145 if (parentRecipe instanceof IndexerBasedAggregatorRecipe
146 || parentRecipe instanceof SingleColumnAggregatorRecipe)
147 throw new IllegalArgumentException(
148 "Cannot take projection indexer of aggregator node at plan " + planToCompile);
149 IndexerRecipe recipe = RecipesHelper.projectionIndexerRecipe(parentRecipe, toRecipeMask(mask));
150 // final List<PVariable> maskedVariables = mask.transform(parentTrace.getVariablesTuple());
151 return new PlanningTrace(planToCompile, /* maskedVariables */ parentTrace.getVariablesTuple(), recipe,
152 parentTrace);
153 // TODO add specialized indexer trace info?
154 }
155
156 /**
157 * Creates a trimmer that keeps selected variables only.
158 */
159 protected static TrimmerRecipe makeTrimmerRecipe(final PlanningTrace compiledParent,
160 List<PVariable> projectedVariables) {
161 final Mask projectionMask = makeProjectionMask(compiledParent, projectedVariables);
162 final TrimmerRecipe trimmerRecipe = ReteRecipeCompiler.FACTORY.createTrimmerRecipe();
163 trimmerRecipe.setParent(compiledParent.getRecipe());
164 trimmerRecipe.setMask(projectionMask);
165 return trimmerRecipe;
166 }
167
168 public static Mask makeProjectionMask(final PlanningTrace compiledParent, Iterable<PVariable> projectedVariables) {
169 List<Integer> projectionSourceIndices = new ArrayList<Integer>();
170 for (PVariable pVariable : projectedVariables) {
171 projectionSourceIndices.add(compiledParent.getPosMapping().get(pVariable));
172 }
173 final Mask projectionMask = RecipesHelper.mask(compiledParent.getRecipe().getArity(), projectionSourceIndices);
174 return projectionMask;
175 }
176
177 /**
178 * @since 1.6
179 */
180 public static final class PosetTriplet {
181 public Mask coreMask;
182 public Mask posetMask;
183 public IPosetComparator comparator;
184 }
185
186 /**
187 * @since 1.6
188 */
189 public static PosetTriplet computePosetInfo(List<PVariable> variables, PBody body, IQueryMetaContext context) {
190 Map<PVariable, Set<IInputKey>> typeMap = TypeHelper.inferUnaryTypesFor(variables, body.getConstraints(),
191 context);
192 List<Set<IInputKey>> keys = new LinkedList<Set<IInputKey>>();
193
194 for (int i = 0; i < variables.size(); i++) {
195 keys.add(typeMap.get(variables.get(i)));
196 }
197
198 return computePosetInfo(keys, context);
199 }
200
201 /**
202 * @since 1.6
203 */
204 public static PosetTriplet computePosetInfo(List<PParameter> parameters, IQueryMetaContext context) {
205 List<Set<IInputKey>> keys = new LinkedList<Set<IInputKey>>();
206 for (int i = 0; i < parameters.size(); i++) {
207 IInputKey key = parameters.get(i).getDeclaredUnaryType();
208 if (key == null) {
209 keys.add(Collections.emptySet());
210 } else {
211 keys.add(Collections.singleton(parameters.get(i).getDeclaredUnaryType()));
212 }
213 }
214 return computePosetInfo(keys, context);
215 }
216
217
218
219 /**
220 * @since 1.6
221 */
222 public static PosetTriplet computePosetInfo(Iterable<Set<IInputKey>> keys, IQueryMetaContext context) {
223 PosetTriplet result = new PosetTriplet();
224 List<Integer> coreIndices = new ArrayList<Integer>();
225 List<Integer> posetIndices = new ArrayList<Integer>();
226 List<IInputKey> filtered = new ArrayList<IInputKey>();
227 boolean posetKey = false;
228 int index = -1;
229
230 for (Set<IInputKey> _keys : keys) {
231 ++index;
232 posetKey = false;
233
234 for (IInputKey key : _keys) {
235 if (key != null && context.isPosetKey(key)) {
236 posetKey = true;
237 filtered.add(key);
238 break;
239 }
240 }
241
242 if (posetKey) {
243 posetIndices.add(index);
244 } else {
245 coreIndices.add(index);
246 }
247 }
248
249 result.comparator = context.getPosetComparator(filtered);
250 result.coreMask = RecipesHelper.mask(index + 1, coreIndices);
251 result.posetMask = RecipesHelper.mask(index + 1, posetIndices);
252
253 return result;
254 }
255
256 /**
257 * Creates a recipe for a production node and the corresponding trace.
258 * <p> PRE: in case this is a recursion cutoff point (see {@link RecursionCutoffPoint})
259 * and bodyFinalTraces will be filled later,
260 * the object yielded now by bodyFinalTraces.values() must return up-to-date results later
261 * @since 2.4
262 */
263 public static CompiledQuery makeQueryTrace(PQuery query, Map<PBody, RecipeTraceInfo> bodyFinalTraces,
264 Collection<ReteNodeRecipe> bodyFinalRecipes, QueryEvaluationHint hint, IQueryMetaContext context,
265 boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) {
266 ProductionRecipe recipe = ReteRecipeCompiler.FACTORY.createProductionRecipe();
267
268 // temporary solution to support the deprecated option for now
269 boolean deleteAndRederiveEvaluationDep = deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(hint);
270
271 recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep);
272
273 if (deleteAndRederiveEvaluationDep || (timelyEvaluation != null)) {
274 PosetTriplet triplet = computePosetInfo(query.getParameters(), context);
275 if (triplet.comparator != null) {
276 MonotonicityInfo info = FACTORY.createMonotonicityInfo();
277 info.setCoreMask(triplet.coreMask);
278 info.setPosetMask(triplet.posetMask);
279 info.setPosetComparator(triplet.comparator);
280 recipe.setOptionalMonotonicityInfo(info);
281 }
282 }
283
284 recipe.setPattern(query);
285 recipe.setPatternFQN(query.getFullyQualifiedName());
286 recipe.setTraceInfo(recipe.getPatternFQN());
287 recipe.getParents().addAll(bodyFinalRecipes);
288 for (int i = 0; i < query.getParameterNames().size(); ++i) {
289 recipe.getMappedIndices().put(query.getParameterNames().get(i), i);
290 }
291
292 return new CompiledQuery(recipe, bodyFinalTraces, query);
293 }
294
295 /**
296 * Calculated index mappings for a join, based on the common variables of the two parent subplans.
297 *
298 * @author Gabor Bergmann
299 *
300 */
301 public static class JoinHelper {
302 private TupleMask primaryMask;
303 private TupleMask secondaryMask;
304 private TupleMask complementerMask;
305 private RecipeTraceInfo primaryIndexer;
306 private RecipeTraceInfo secondaryIndexer;
307 private JoinRecipe naturalJoinRecipe;
308 private List<PVariable> naturalJoinVariablesTuple;
309
310 /**
311 * @pre enforceVariableCoincidences() has been called on both sides.
312 */
313 public JoinHelper(SubPlan planToCompile, PlanningTrace primaryCompiled, PlanningTrace callTrace) {
314 super();
315
316 Set<PVariable> primaryVariables = new LinkedHashSet<PVariable>(primaryCompiled.getVariablesTuple());
317 Set<PVariable> secondaryVariables = new LinkedHashSet<PVariable>(callTrace.getVariablesTuple());
318 int oldNodes = 0;
319 Set<Integer> introducingSecondaryIndices = new TreeSet<Integer>();
320 for (PVariable var : secondaryVariables) {
321 if (primaryVariables.contains(var))
322 oldNodes++;
323 else
324 introducingSecondaryIndices.add(callTrace.getPosMapping().get(var));
325 }
326 List<Integer> primaryIndices = new ArrayList<Integer>(oldNodes);
327 List<Integer> secondaryIndices = new ArrayList<Integer>(oldNodes);
328 for (PVariable var : secondaryVariables) {
329 if (primaryVariables.contains(var)) {
330 primaryIndices.add(primaryCompiled.getPosMapping().get(var));
331 secondaryIndices.add(callTrace.getPosMapping().get(var));
332 }
333 }
334 Collection<Integer> complementerIndices = introducingSecondaryIndices;
335
336 primaryMask = TupleMask.fromSelectedIndices(primaryCompiled.getVariablesTuple().size(), primaryIndices);
337 secondaryMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), secondaryIndices);
338 complementerMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), complementerIndices);
339
340 primaryIndexer = makeIndexerTrace(planToCompile, primaryCompiled, primaryMask);
341 secondaryIndexer = makeIndexerTrace(planToCompile, callTrace, secondaryMask);
342
343 naturalJoinRecipe = FACTORY.createJoinRecipe();
344 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
345 naturalJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe());
346 naturalJoinRecipe.setRightParentComplementaryMask(CompilerHelper.toRecipeMask(complementerMask));
347
348 naturalJoinVariablesTuple = new ArrayList<PVariable>(primaryCompiled.getVariablesTuple());
349 for (int complementerIndex : complementerMask.indices)
350 naturalJoinVariablesTuple.add(callTrace.getVariablesTuple().get(complementerIndex));
351 }
352
353 public TupleMask getPrimaryMask() {
354 return primaryMask;
355 }
356
357 public TupleMask getSecondaryMask() {
358 return secondaryMask;
359 }
360
361 public TupleMask getComplementerMask() {
362 return complementerMask;
363 }
364
365 public RecipeTraceInfo getPrimaryIndexer() {
366 return primaryIndexer;
367 }
368
369 public RecipeTraceInfo getSecondaryIndexer() {
370 return secondaryIndexer;
371 }
372
373 public JoinRecipe getNaturalJoinRecipe() {
374 return naturalJoinRecipe;
375 }
376
377 public List<PVariable> getNaturalJoinVariablesTuple() {
378 return naturalJoinVariablesTuple;
379 }
380
381 }
382
383 /**
384 * @since 1.4
385 */
386 public static Mask toRecipeMask(TupleMask mask) {
387 return RecipesHelper.mask(mask.sourceWidth, mask.indices);
388 }
389
390}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java
new file mode 100644
index 00000000..7d1e4d3a
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.construction.plancompiler;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15import java.util.stream.Collectors;
16
17import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
18import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
21import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
22import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe;
23import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
24import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery;
25import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
26
27/**
28 * In a recursive query structure, query composition references can be cut off so that the remaining structure is DAG.
29 * {@link RecursionCutoffPoint} represents one such cut off query composition.
30 * When the compilation of the recursive query finishes and the compiled form becomes available,
31 * the {@link RecursionCutoffPoint} has to be signaled to update parent traces and recipes of the recursive call.
32 *
33 * @author Bergmann Gabor
34 * @noreference This class is not intended to be referenced by clients
35 *
36 */
37public class RecursionCutoffPoint {
38 final Map<PBody, RecipeTraceInfo> futureTraceMap;
39 final CompiledQuery compiledQuery;
40 final ProductionRecipe recipe;
41 final QueryEvaluationHint hint;
42
43 public RecursionCutoffPoint(PQuery query, QueryEvaluationHint hint, IQueryMetaContext context, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) {
44 super();
45 this.hint = hint;
46 this.futureTraceMap = new HashMap<>(); // IMPORTANT: the identity of futureTraceMap.values() will not change
47 this.compiledQuery = CompilerHelper.makeQueryTrace(query, futureTraceMap, Collections.<ReteNodeRecipe>emptySet(), hint, context, deleteAndRederiveEvaluation, timelyEvaluation);
48 this.recipe = (ProductionRecipe)compiledQuery.getRecipe();
49 if (!compiledQuery.getParentRecipeTraces().isEmpty()) {
50 throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has trace parents: %s",
51 compiledQuery.getQuery(),
52 prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces())));
53 }
54 if (!recipe.getParents().isEmpty()) {
55 throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has recipe parents: %s",
56 compiledQuery.getQuery(),
57 prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces())));
58 }
59 }
60
61
62
63 private String prettyPrintParentRecipeTraces(List<RecipeTraceInfo> trace) {
64 return trace.stream().map(Object::toString).collect(Collectors.joining(", "));
65 }
66
67 /**
68 * Signals that compilation of the recursive query has terminated, culminating into the given compiled form.
69 * The query composition that has been cut off will be connected now.
70 */
71 public void mend(CompiledQuery finalCompiledForm) {
72 futureTraceMap.putAll(finalCompiledForm.getParentRecipeTracesPerBody());
73 recipe.getParents().addAll(((ProductionRecipe)finalCompiledForm.getRecipe()).getParents());
74 }
75
76 public CompiledQuery getCompiledQuery() {
77 return compiledQuery;
78 }
79
80 public ProductionRecipe getRecipe() {
81 return recipe;
82 }
83
84
85
86}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java
new file mode 100644
index 00000000..5df3a971
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java
@@ -0,0 +1,949 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.rete.construction.plancompiler;
11
12import org.apache.log4j.Logger;
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions;
15import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
16import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
17import tools.refinery.viatra.runtime.matchers.context.IInputKey;
18import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
19import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext;
20import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
21import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy;
22import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
23import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper;
24import tools.refinery.viatra.runtime.matchers.planning.operations.*;
25import tools.refinery.viatra.runtime.matchers.psystem.*;
26import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
27import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
28import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*;
29import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*;
30import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
31import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
32import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility;
33import tools.refinery.viatra.runtime.matchers.psystem.rewriters.*;
34import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
35import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
36import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
37import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
38import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
39import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
40import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.JoinHelper;
41import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.PosetTriplet;
42import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
43import tools.refinery.viatra.runtime.rete.recipes.*;
44import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper;
45import tools.refinery.viatra.runtime.rete.traceability.*;
46import tools.refinery.viatra.runtime.rete.util.ReteHintOptions;
47
48import java.util.*;
49import java.util.Map.Entry;
50import java.util.function.BiFunction;
51import java.util.function.Function;
52
53/**
54 * Compiles queries and query plans into Rete recipes, traced by respectively a {@link CompiledQuery} or
55 * {@link CompiledSubPlan}.
56 *
57 * @author Bergmann Gabor
58 *
59 */
60public class ReteRecipeCompiler {
61
62 private final IQueryPlannerStrategy plannerStrategy;
63 private final IQueryMetaContext metaContext;
64 private final IQueryBackendHintProvider hintProvider;
65 private final PDisjunctionRewriter normalizer;
66 private final QueryAnalyzer queryAnalyzer;
67 private final Logger logger;
68
69 /**
70 * @since 2.2
71 */
72 protected final boolean deleteAndRederiveEvaluation;
73 /**
74 * @since 2.4
75 */
76 protected final TimelyConfiguration timelyEvaluation;
77
78 /**
79 * @since 1.5
80 */
81 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext,
82 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer) {
83 this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null);
84 }
85
86 /**
87 * @since 2.4
88 */
89 public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext,
90 IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer,
91 boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) {
92 super();
93 this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation;
94 this.timelyEvaluation = timelyEvaluation;
95 this.plannerStrategy = plannerStrategy;
96 this.logger = logger;
97 this.metaContext = metaContext;
98 this.queryAnalyzer = queryAnalyzer;
99 this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(),
100 new PBodyNormalizer(metaContext) {
101
102 @Override
103 protected boolean shouldExpandWeakenedAlternatives(PQuery query) {
104 QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query);
105 Boolean expandWeakenedAlternativeConstraints = ReteHintOptions.expandWeakenedAlternativeConstraints
106 .getValueOrDefault(hint);
107 return expandWeakenedAlternativeConstraints;
108 }
109
110 });
111 this.hintProvider = hintProvider;
112 }
113
114 static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE;
115
116 // INTERNALLY CACHED
117 private Map<PBody, SubPlan> plannerCache = new HashMap<PBody, SubPlan>();
118 private Set<PBody> planningInProgress = new HashSet<PBody>();
119
120 private Map<PQuery, CompiledQuery> queryCompilerCache = new HashMap<PQuery, CompiledQuery>();
121 private Set<PQuery> compilationInProgress = new HashSet<PQuery>();
122 private IMultiLookup<PQuery, RecursionCutoffPoint> recursionCutoffPoints = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
123 private Map<SubPlan, CompiledSubPlan> subPlanCompilerCache = new HashMap<SubPlan, CompiledSubPlan>();
124 private Map<ReteNodeRecipe, SubPlan> compilerBackTrace = new HashMap<ReteNodeRecipe, SubPlan>();
125
126 /**
127 * Clears internal state
128 */
129 public void reset() {
130 plannerCache.clear();
131 planningInProgress.clear();
132 queryCompilerCache.clear();
133 subPlanCompilerCache.clear();
134 compilerBackTrace.clear();
135 }
136
137 /**
138 * Returns a {@link CompiledQuery} compiled from a query
139 * @throws ViatraQueryRuntimeException
140 */
141 public CompiledQuery getCompiledForm(PQuery query) {
142 CompiledQuery compiled = queryCompilerCache.get(query);
143 if (compiled == null) {
144
145 IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector
146 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query));
147 if (traceCollector != null) {
148 traceCollector.addTrace(query, query);
149 }
150
151 boolean reentrant = !compilationInProgress.add(query);
152 if (reentrant) { // oops, recursion into body in progress
153 RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext,
154 deleteAndRederiveEvaluation, timelyEvaluation);
155 recursionCutoffPoints.addPair(query, cutoffPoint);
156 return cutoffPoint.getCompiledQuery();
157 } else { // not reentrant, therefore no recursion, do the compilation
158 try {
159 compiled = compileProduction(query);
160 queryCompilerCache.put(query, compiled);
161 // backTrace.put(compiled.getRecipe(), plan);
162
163 // if this was a recursive query, mend all points where recursion was cut off
164 for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) {
165 cutoffPoint.mend(compiled);
166 }
167 } finally {
168 compilationInProgress.remove(query);
169 }
170 }
171 }
172 return compiled;
173 }
174
175 /**
176 * Returns a {@link CompiledSubPlan} compiled from a query plan
177 * @throws ViatraQueryRuntimeException
178 */
179 public CompiledSubPlan getCompiledForm(SubPlan plan) {
180 CompiledSubPlan compiled = subPlanCompilerCache.get(plan);
181 if (compiled == null) {
182 compiled = doCompileDispatch(plan);
183 subPlanCompilerCache.put(plan, compiled);
184 compilerBackTrace.put(compiled.getRecipe(), plan);
185 }
186 return compiled;
187 }
188
189 /**
190 * @throws ViatraQueryRuntimeException
191 */
192 public SubPlan getPlan(PBody pBody) {
193 // if the query is not marked as being compiled, initiate compilation
194 // (this is useful in case of recursion if getPlan() is the entry point)
195 PQuery pQuery = pBody.getPattern();
196 if (!compilationInProgress.contains(pQuery))
197 getCompiledForm(pQuery);
198
199 // Is the plan already cached?
200 SubPlan plan = plannerCache.get(pBody);
201 if (plan == null) {
202 boolean reentrant = !planningInProgress.add(pBody);
203 if (reentrant) { // oops, recursion into body in progress
204 throw new IllegalArgumentException(
205 "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName());
206 } else { // not reentrant, therefore no recursion, do the planning
207 try {
208 plan = plannerStrategy.plan(pBody, logger, metaContext);
209 plannerCache.put(pBody, plan);
210 } finally {
211 planningInProgress.remove(pBody);
212 }
213 }
214 }
215 return plan;
216 }
217
218 private CompiledQuery compileProduction(PQuery query) {
219 Collection<SubPlan> bodyPlans = new ArrayList<SubPlan>();
220 normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector
221 .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)));
222 for (PBody pBody : normalizer.rewrite(query).getBodies()) {
223 SubPlan bodyPlan = getPlan(pBody);
224 bodyPlans.add(bodyPlan);
225 }
226 return doCompileProduction(query, bodyPlans);
227 }
228
229 private CompiledQuery doCompileProduction(PQuery query, Collection<SubPlan> bodies) {
230 // TODO skip production node if there is just one body and no projection needed?
231 Map<PBody, RecipeTraceInfo> bodyFinalTraces = new HashMap<PBody, RecipeTraceInfo>();
232 Collection<ReteNodeRecipe> bodyFinalRecipes = new HashSet<ReteNodeRecipe>();
233
234 for (SubPlan bodyFinalPlan : bodies) {
235 // skip over any projections at the end
236 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan);
237
238 // TODO checkAndTrimEqualVariables may introduce superfluous trim,
239 // but whatever (no uniqueness enforcer needed)
240
241 // compile body
242 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan);
243
244 // project to parameter list
245 RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false);
246
247 bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace);
248 bodyFinalRecipes.add(finalTrace.getRecipe());
249 }
250
251 CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes,
252 getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation);
253
254 return compiled;
255 }
256
257 private CompiledSubPlan doCompileDispatch(SubPlan plan) {
258 final POperation operation = plan.getOperation();
259 if (operation instanceof PEnumerate) {
260 return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan);
261 } else if (operation instanceof PApply) {
262 final PConstraint pConstraint = ((PApply) operation).getPConstraint();
263 if (pConstraint instanceof EnumerablePConstraint) {
264 CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0));
265 PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint);
266 return compileToNaturalJoin(plan, primaryParent, secondaryParent);
267 } else if (pConstraint instanceof DeferredPConstraint) {
268 return doDeferredDispatch((DeferredPConstraint) pConstraint, plan);
269 } else {
270 throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString());
271 }
272 } else if (operation instanceof PJoin) {
273 return doCompileJoin((PJoin) operation, plan);
274 } else if (operation instanceof PProject) {
275 return doCompileProject((PProject) operation, plan);
276 } else if (operation instanceof PStart) {
277 return doCompileStart((PStart) operation, plan);
278 } else {
279 throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString());
280 }
281 }
282
283 private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) {
284 final SubPlan parentPlan = plan.getParentPlans().get(0);
285 final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan);
286 if (constraint instanceof Equality) {
287 return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled);
288 } else if (constraint instanceof ExportedParameter) {
289 return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled);
290 } else if (constraint instanceof Inequality) {
291 return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled);
292 } else if (constraint instanceof NegativePatternCall) {
293 return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled);
294 } else if (constraint instanceof PatternMatchCounter) {
295 return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled);
296 } else if (constraint instanceof AggregatorConstraint) {
297 return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled);
298 } else if (constraint instanceof ExpressionEvaluation) {
299 return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled);
300 } else if (constraint instanceof TypeFilterConstraint) {
301 return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled);
302 }
303 throw new UnsupportedOperationException("Unknown deferred constraint " + constraint);
304 }
305
306 private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan,
307 CompiledSubPlan parentCompiled) {
308 if (constraint.isMoot())
309 return parentCompiled.cloneFor(plan);
310
311 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho());
312 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom());
313
314 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) {
315 Integer indexLower = Math.min(index1, index2);
316 Integer indexHigher = Math.max(index1, index2);
317
318 EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe();
319 equalityFilterRecipe.setParent(parentCompiled.getRecipe());
320 equalityFilterRecipe.getIndices().add(indexLower);
321 equalityFilterRecipe.getIndices().add(indexHigher);
322
323 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled);
324 } else {
325 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s",
326 plan.toShortString(), parentCompiled.toString()));
327 }
328 }
329
330 /**
331 * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in
332 * toFilterTrace.
333 */
334 private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace,
335 ConstantRecipe constantRecipe, List<PVariable> filteredVariables) {
336 PlanningTrace resultTrace = toFilterTrace;
337
338 int constantVariablesSize = filteredVariables.size();
339 for (int i = 0; i < constantVariablesSize; ++i) {
340 Object constantValue = constantRecipe.getConstantValues().get(i);
341 PVariable filteredVariable = filteredVariables.get(i);
342 int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable);
343
344 DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe();
345 dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn);
346 dispatcherRecipe.setParent(resultTrace.getRecipe());
347
348 PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe,
349 resultTrace);
350
351 DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe();
352 bucketRecipe.setBucketKey(constantValue);
353 bucketRecipe.setParent(dispatcherRecipe);
354
355 PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe,
356 dispatcherTrace);
357
358 resultTrace = bucketTrace;
359 }
360
361 return resultTrace.cloneFor(plan);
362 }
363
364 private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan,
365 CompiledSubPlan parentCompiled) {
366 return parentCompiled.cloneFor(plan);
367 }
368
369 private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan,
370 CompiledSubPlan parentCompiled) {
371 if (constraint.isEliminable())
372 return parentCompiled.cloneFor(plan);
373
374 Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho());
375 Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom());
376
377 if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) {
378 Integer indexLower = Math.min(index1, index2);
379 Integer indexHigher = Math.max(index1, index2);
380
381 InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe();
382 inequalityFilterRecipe.setParent(parentCompiled.getRecipe());
383 inequalityFilterRecipe.setSubject(indexLower);
384 inequalityFilterRecipe.getInequals().add(indexHigher);
385
386 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe,
387 parentCompiled);
388 } else {
389 throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s",
390 plan.toShortString(), parentCompiled.toString()));
391 }
392 }
393
394 private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan,
395 CompiledSubPlan parentCompiled) {
396 final IInputKey inputKey = constraint.getInputKey();
397 if (!metaContext.isStateless(inputKey))
398 throw new UnsupportedOperationException(
399 "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike "
400 + inputKey);
401
402 final Tuple constraintVariables = constraint.getVariablesTuple();
403 final List<PVariable> parentVariables = parentCompiled.getVariablesTuple();
404
405 Mask mask; // select elements of the tuple to check against extensional relation
406 if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) {
407 mask = null; // lucky case, parent signature equals that of input key
408 } else {
409 List<PVariable> variables = new ArrayList<PVariable>();
410 for (Object variable : constraintVariables.getElements()) {
411 variables.add((PVariable) variable);
412 }
413 mask = CompilerHelper.makeProjectionMask(parentCompiled, variables);
414 }
415 InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey,
416 inputKey.getStringID(), mask);
417 return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled);
418 }
419
420 private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan,
421 CompiledSubPlan parentCompiled) {
422 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
423 constraint.getActualParametersTuple());
424
425 JoinHelper joinHelper = new JoinHelper(plan, parentCompiled, callTrace);
426 final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer();
427 final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer();
428
429 AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe();
430 antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
431 antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe());
432
433 return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer,
434 secondaryIndexer);
435 }
436
437 private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan,
438 CompiledSubPlan parentCompiled) {
439 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
440 constraint.getActualParametersTuple());
441
442 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query
443 JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace);
444 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer();
445 final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer();
446
447 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>(
448 fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple()));
449 /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable());
450
451 CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe();
452 aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe());
453 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe,
454 callProjectionIndexer);
455
456 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe();
457 aggregatorIndexerRecipe.setParent(aggregatorRecipe);
458 // aggregatorIndexerRecipe.setMask(RecipesHelper.mask(
459 // sideVariablesTuple.size(),
460 // //use same indices as in the projection indexer
461 // // EVEN if result variable already visible in left parent
462 // fakeJoinHelper.getSecondaryMask().indices
463 // ));
464
465 int aggregatorWidth = sideVariablesTuple.size();
466 int aggregateResultIndex = aggregatorWidth - 1;
467
468 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit(
469 // aggregate according all but the last index
470 aggregateResultIndex, aggregatorWidth)));
471 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe,
472 aggregatorTrace);
473
474 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe();
475 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
476 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe);
477 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth,
478 // extend with last element only - the computation value
479 aggregateResultIndex));
480
481 // what if the new variable already has a value?
482 // even if already known, we add the new result variable, so that it can be filtered at the end
483 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable());
484
485 final List<PVariable> aggregatedVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
486 aggregatedVariablesTuple.add(constraint.getResultVariable());
487
488 PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer,
489 aggregatorIndexerTrace);
490
491 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan);
492 // if (!alreadyKnown) {
493 // return joinTrace.cloneFor(plan);
494 // } else {
495 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple());
496 // }
497 }
498
499 private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan,
500 CompiledSubPlan parentCompiled) {
501 final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan,
502 constraint.getActualParametersTuple());
503
504 // hack: use some mask computations (+ the indexers) from a fake natural join against the called query
505 JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace);
506 final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer();
507 TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask();
508
509 final List<PVariable> sideVariablesTuple = new ArrayList<PVariable>(
510 callGroupMask.transform(callTrace.getVariablesTuple()));
511 /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable());
512
513 IMultisetAggregationOperator<?, ?, ?> operator = constraint.getAggregator().getOperator();
514
515 SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe();
516 columnAggregatorRecipe.setParent(callTrace.getRecipe());
517 columnAggregatorRecipe.setMultisetAggregationOperator(operator);
518
519 int columnIndex = constraint.getAggregatedColumn();
520 IPosetComparator posetComparator = null;
521 Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask);
522
523 // temporary solution to support the deprecated option for now
524 final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan));
525
526 columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep);
527 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) {
528 List<PParameter> parameters = constraint.getReferredQuery().getParameters();
529 IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType();
530 if (key != null && metaContext.isPosetKey(key)) {
531 posetComparator = metaContext.getPosetComparator(Collections.singleton(key));
532 }
533 }
534
535 if (posetComparator == null) {
536 columnAggregatorRecipe.setGroupByMask(groupMask);
537 columnAggregatorRecipe.setAggregableIndex(columnIndex);
538 } else {
539 MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo();
540 monotonicityInfo.setCoreMask(groupMask);
541 monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask(
542 TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize())));
543 monotonicityInfo.setPosetComparator(posetComparator);
544 columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo);
545 }
546
547 ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe;
548 PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace);
549
550 IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe();
551 aggregatorIndexerRecipe.setParent(aggregatorRecipe);
552
553 int aggregatorWidth = sideVariablesTuple.size();
554 int aggregateResultIndex = aggregatorWidth - 1;
555
556 aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit(
557 // aggregate according all but the last index
558 aggregateResultIndex, aggregatorWidth)));
559 PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe,
560 aggregatorTrace);
561
562 JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe();
563 naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe());
564 naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe);
565 naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth,
566 // extend with last element only - the computation value
567 aggregateResultIndex));
568
569 // what if the new variable already has a value?
570 // even if already known, we add the new result variable, so that it can be filtered at the end
571 // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable());
572
573 final List<PVariable> finalVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
574 finalVariablesTuple.add(constraint.getResultVariable());
575
576 PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer,
577 aggregatorIndexerTrace);
578
579 return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan);
580 // if (!alreadyKnown) {
581 // return joinTrace.cloneFor(plan);
582 // } else {
583 // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple());
584 // }
585 }
586
587 private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan,
588 CompiledSubPlan parentCompiled) {
589 Map<String, Integer> tupleNameMap = new HashMap<String, Integer>();
590 for (String name : constraint.getEvaluator().getInputParameterNames()) {
591 Map<? extends Object, Integer> index = parentCompiled.getPosMapping();
592 PVariable variable = constraint.getPSystem().getVariableByNameChecked(name);
593 Integer position = index.get(variable);
594 tupleNameMap.put(name, position);
595 }
596
597 final PVariable outputVariable = constraint.getOutputVariable();
598 final boolean booleanCheck = outputVariable == null;
599
600 // TODO determine whether expression is costly
601 boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan));
602 // for (PAnnotation pAnnotation :
603 // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) {
604 // for (Object value : pAnnotation.getAllValues("expensive")) {
605 // if (value instanceof Boolean)
606 // cacheOutput = (boolean) value;
607 // }
608 // }
609
610 ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe()
611 : FACTORY.createEvalRecipe();
612 enforcerRecipe.setParent(parentCompiled.getRecipe());
613 enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator()));
614 enforcerRecipe.setCacheOutput(cacheOutput);
615 if (enforcerRecipe instanceof EvalRecipe) {
616 ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding());
617 }
618 for (Entry<String, Integer> entry : tupleNameMap.entrySet()) {
619 enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue());
620 }
621
622 final List<PVariable> enforcerVariablesTuple = new ArrayList<PVariable>(parentCompiled.getVariablesTuple());
623 if (!booleanCheck)
624 enforcerVariablesTuple.add(outputVariable);
625 PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled);
626
627 return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan);
628 }
629
630 private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) {
631 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan);
632 final CompiledSubPlan leftCompiled = compiledParents.get(0);
633 final CompiledSubPlan rightCompiled = compiledParents.get(1);
634
635 return compileToNaturalJoin(plan, leftCompiled, rightCompiled);
636 }
637
638 private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled,
639 final PlanningTrace rightCompiled) {
640 // CHECK IF SPECIAL CASE
641
642 // Is constant filtering applicable?
643 if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) {
644 if (leftCompiled.getRecipe() instanceof ConstantRecipe
645 && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) {
646 return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(),
647 leftCompiled.getVariablesTuple());
648 }
649 if (rightCompiled.getRecipe() instanceof ConstantRecipe
650 && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) {
651 return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(),
652 rightCompiled.getVariablesTuple());
653 }
654 }
655
656 // ELSE: ACTUAL JOIN
657 JoinHelper joinHelper = new JoinHelper(plan, leftCompiled, rightCompiled);
658 return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(),
659 joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer());
660 }
661
662 private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) {
663 final List<CompiledSubPlan> compiledParents = getCompiledFormOfParents(plan);
664 final CompiledSubPlan compiledParent = compiledParents.get(0);
665
666 List<PVariable> projectedVariables = new ArrayList<PVariable>(operation.getToVariables());
667 // Determinizing projection: try to keep original order (hopefully facilitates node reuse)
668 Map<PVariable, Integer> parentPosMapping = compiledParent.getPosMapping();
669 Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get));
670
671 return doProjectPlan(compiledParent, projectedVariables, true,
672 parentTrace -> parentTrace.cloneFor(plan),
673 (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace),
674 (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace)
675 );
676 }
677
678 /**
679 * Projects a subplan onto the specified variable tuple
680 * @param compiledParentPlan the compiled form of the subplan
681 * @param targetVariables list of variables to project to
682 * @param enforceUniqueness whether distinctness shall be enforced after the projection.
683 * Specify false only if directly connecting to a production node.
684 * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan, in case it is sufficient
685 * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the node and its parent trace
686 * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the node and its parent trace
687 * @since 2.1
688 */
689 <ResultTrace extends RecipeTraceInfo> ResultTrace doProjectPlan(
690 final CompiledSubPlan compiledParentPlan,
691 final List<PVariable> targetVariables,
692 boolean enforceUniqueness,
693 Function<CompiledSubPlan, ResultTrace> reinterpretTraceFactory,
694 BiFunction<ReteNodeRecipe, RecipeTraceInfo, RecipeTraceInfo> intermediateTraceFactory,
695 BiFunction<ReteNodeRecipe, RecipeTraceInfo, ResultTrace> finalTraceFactory)
696 {
697 if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed
698 return reinterpretTraceFactory.apply(compiledParentPlan);
699
700 // otherwise, we need at least a trimmer
701 TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables);
702
703 // do we need to eliminate duplicates?
704 SubPlan parentPlan = compiledParentPlan.getSubPlan();
705 if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined(
706 parentPlan,
707 targetVariables,
708 queryAnalyzer,
709 true))
710 {
711 // if uniqueness enforcess is unwanted or unneeeded, skip it
712 return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan);
713 } else {
714 // add a uniqueness enforcer
715 UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe();
716 recipe.getParents().add(trimmerRecipe);
717
718 // temporary solution to support the deprecated option for now
719 final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan));
720
721 recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep);
722 if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) {
723 PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables, parentPlan.getBody(), metaContext);
724
725 if (triplet.comparator != null) {
726 MonotonicityInfo info = FACTORY.createMonotonicityInfo();
727 info.setCoreMask(triplet.coreMask);
728 info.setPosetMask(triplet.posetMask);
729 info.setPosetComparator(triplet.comparator);
730 recipe.setOptionalMonotonicityInfo(info);
731 }
732 }
733
734 RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan);
735 return finalTraceFactory.apply(recipe, trimmerTrace);
736 }
737 }
738
739 /**
740 * Projects the final compiled form of a PBody onto the parameter tuple
741 * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to query parameters
742 * @param enforceUniqueness whether distinctness shall be enforced after the projection.
743 * Specify false only if directly connecting to a production node.
744 * @since 2.1
745 */
746 RecipeTraceInfo projectBodyFinalToParameters(
747 final CompiledSubPlan compiledBody,
748 boolean enforceUniqueness)
749 {
750 final PBody body = compiledBody.getSubPlan().getBody();
751 final List<PVariable> parameterList = body.getSymbolicParameterVariables();
752
753 return doProjectPlan(compiledBody, parameterList, enforceUniqueness,
754 parentTrace -> parentTrace,
755 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace),
756 (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace)
757 );
758 }
759
760 private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) {
761 if (!operation.getAPrioriVariables().isEmpty()) {
762 throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString());
763 }
764 final ConstantRecipe recipe = FACTORY.createConstantRecipe();
765 recipe.getConstantValues().clear();
766
767 return new CompiledSubPlan(plan, new ArrayList<PVariable>(), recipe);
768 }
769
770 private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) {
771 final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan);
772
773 return trimmedTrace.cloneFor(plan);
774 }
775
776 private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) {
777 final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint);
778 final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace);
779 return trimmedTrace;
780 }
781
782 private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) {
783 if (constraint instanceof RelationEvaluation) {
784 return compileEnumerable(plan, (RelationEvaluation) constraint);
785 } else if (constraint instanceof BinaryTransitiveClosure) {
786 return compileEnumerable(plan, (BinaryTransitiveClosure) constraint);
787 } else if (constraint instanceof BinaryReflexiveTransitiveClosure) {
788 return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint);
789 } else if (constraint instanceof RepresentativeElectionConstraint) {
790 return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint);
791 } else if (constraint instanceof ConstantValue) {
792 return compileEnumerable(plan, (ConstantValue) constraint);
793 } else if (constraint instanceof PositivePatternCall) {
794 return compileEnumerable(plan, (PositivePatternCall) constraint);
795 } else if (constraint instanceof TypeConstraint) {
796 return compileEnumerable(plan, (TypeConstraint) constraint);
797 }
798 throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint);
799 }
800
801 private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) {
802 // TODO the implementation would perform better if an inequality check would be used after tcRecipe and
803 // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not available
804 // in recipe metamodel in VIATRA 2.0
805
806 // Find called query
807 final PQuery referredQuery = constraint.getSupplierKey();
808 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple());
809
810 // Calculate irreflexive transitive closure
811 final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe();
812 tcRecipe.setParent(callTrace.getRecipe());
813 final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), tcRecipe, callTrace);
814
815 // Enumerate universe type
816 final IInputKey inputKey = constraint.getUniverseType();
817 final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity());
818 final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(
819 Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe);
820
821 // Calculate reflexive access by duplicating universe type column
822 final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe();
823 reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0));
824 reflexiveRecipe.setParent(universeTypeRecipe);
825 final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), reflexiveRecipe, universeTypeTrace);
826
827 // Finally, reduce duplicates after a join
828 final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe();
829 brtcRecipe.getParents().add(tcRecipe);
830 brtcRecipe.getParents().add(reflexiveRecipe);
831
832 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace, tcTrace);
833 }
834
835 private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) {
836 var referredQuery = constraint.getSupplierKey();
837 var callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple());
838 var recipe = FACTORY.createRepresentativeElectionRecipe();
839 recipe.setParent(callTrace.getRecipe());
840 recipe.setConnectivity(constraint.getConnectivity());
841 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace);
842 }
843
844 private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) {
845 final PQuery referredQuery = constraint.getSupplierKey();
846 final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple());
847
848 final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe();
849 recipe.setParent(callTrace.getRecipe());
850
851 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace);
852 }
853
854 private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) {
855 final List<ReteNodeRecipe> parentRecipes = new ArrayList<ReteNodeRecipe>();
856 final List<RecipeTraceInfo> parentTraceInfos = new ArrayList<RecipeTraceInfo>();
857 for (final PQuery inputQuery : constraint.getReferredQueries()) {
858 final CompiledQuery compiledQuery = getCompiledForm(inputQuery);
859 parentRecipes.add(compiledQuery.getRecipe());
860 parentTraceInfos.add(compiledQuery);
861 }
862 final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe();
863 recipe.getParents().addAll(parentRecipes);
864 recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator()));
865 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos);
866 }
867
868 private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) {
869 final PQuery referredQuery = constraint.getReferredQuery();
870 return referQuery(referredQuery, plan, constraint.getVariablesTuple());
871 }
872
873 private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) {
874 final IInputKey inputKey = constraint.getSupplierKey();
875 final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity());
876 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe);
877 }
878
879 private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) {
880 final ConstantRecipe recipe = FACTORY.createConstantRecipe();
881 recipe.getConstantValues().add(constraint.getSupplierKey());
882 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe);
883 }
884
885 // TODO handle recursion
886 private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) {
887 RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query);
888 return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple),
889 referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning());
890 }
891
892 private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) {
893 // eliminate superfluous production node?
894 if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only
895 Set<PBody> rewrittenBodies = normalizer.rewrite(query).getBodies();
896 if (1 == rewrittenBodies.size()) { // non-disjunctive
897 // TODO in the future, check if non-recursive - (not currently permitted)
898
899 PBody pBody = rewrittenBodies.iterator().next();
900 SubPlan bodyFinalPlan = getPlan(pBody);
901
902 // skip over any projections at the end
903 bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan);
904
905 // TODO checkAndTrimEqualVariables may introduce superfluous trim,
906 // but whatever (no uniqueness enforcer needed)
907
908 // compile body
909 final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan);
910
911 // project to parameter list, add uniqueness enforcer if necessary
912 return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is used */);
913 }
914 }
915
916 // otherwise, regular reference to recipe realizing the query
917 return getCompiledForm(query);
918 }
919
920 protected List<CompiledSubPlan> getCompiledFormOfParents(SubPlan plan) {
921 List<CompiledSubPlan> results = new ArrayList<CompiledSubPlan>();
922 for (SubPlan parentPlan : plan.getParentPlans()) {
923 results.add(getCompiledForm(parentPlan));
924 }
925 return results;
926 }
927
928 /**
929 * Returns an unmodifiable view of currently cached compiled queries.
930 */
931 public Map<PQuery, CompiledQuery> getCachedCompiledQueries() {
932 return Collections.unmodifiableMap(queryCompilerCache);
933 }
934
935 /**
936 * Returns an unmodifiable view of currently cached query plans.
937 */
938 public Map<PBody, SubPlan> getCachedQueryPlans() {
939 return Collections.unmodifiableMap(plannerCache);
940 }
941
942 private QueryEvaluationHint getHints(SubPlan plan) {
943 return getHints(plan.getBody().getPattern());
944 }
945
946 private QueryEvaluationHint getHints(PQuery pattern) {
947 return hintProvider.getQueryEvaluationHint(pattern);
948 }
949}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java
new file mode 100644
index 00000000..45350099
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java
@@ -0,0 +1,162 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction.quasitree;
11
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17import java.util.stream.Collectors;
18import java.util.stream.Stream;
19
20import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
21import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory;
22import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper;
23import tools.refinery.viatra.runtime.matchers.planning.operations.PJoin;
24import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
25import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
26import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
27import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
28
29/**
30 * @author Gabor Bergmann
31 *
32 */
33class JoinCandidate {
34 private QueryAnalyzer analyzer;
35
36 SubPlan primary;
37 SubPlan secondary;
38
39 Set<PVariable> varPrimary;
40 Set<PVariable> varSecondary;
41 Set<PVariable> varCommon;
42
43 List<PConstraint> consPrimary;
44 List<PConstraint> consSecondary;
45
46
47 JoinCandidate(SubPlan primary, SubPlan secondary, QueryAnalyzer analyzer) {
48 super();
49 this.primary = primary;
50 this.secondary = secondary;
51 this.analyzer = analyzer;
52
53 varPrimary = getPrimary().getVisibleVariables();
54 varSecondary = getSecondary().getVisibleVariables();
55 varCommon = CollectionsFactory.createSet(varPrimary);
56 varCommon.retainAll(varSecondary);
57
58 consPrimary = new ArrayList<PConstraint>(primary.getAllEnforcedConstraints());
59 Collections.sort(consPrimary, TieBreaker.CONSTRAINT_COMPARATOR);
60 consSecondary = new ArrayList<PConstraint>(secondary.getAllEnforcedConstraints());
61 Collections.sort(consSecondary, TieBreaker.CONSTRAINT_COMPARATOR);
62 }
63
64
65
66 /**
67 * @return the a
68 */
69 public SubPlan getPrimary() {
70 return primary;
71 }
72
73 /**
74 * @return the b
75 */
76 public SubPlan getSecondary() {
77 return secondary;
78 }
79
80 public SubPlan getJoinedPlan(SubPlanFactory factory) {
81 // check special cases first
82 if (isTrivial())
83 return primary;
84 if (isSubsumption())
85 return
86 (consPrimary.size() > consSecondary.size()) ? primary : secondary;
87
88
89 // default case
90 return factory.createSubPlan(new PJoin(), primary, secondary);
91 }
92
93 @Override
94 public String toString() {
95 return primary.toString() + " |x| " + secondary.toString();
96 }
97
98 /**
99 * @return the varPrimary
100 */
101 public Set<PVariable> getVarPrimary() {
102 return varPrimary;
103 }
104
105 /**
106 * @return the varSecondary
107 */
108 public Set<PVariable> getVarSecondary() {
109 return varSecondary;
110 }
111
112 /**
113 * @return constraints of primary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}.
114 */
115 public List<PConstraint> getConsPrimary() {
116 return consPrimary;
117 }
118 /**
119 * @return constraints of secondary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}.
120 */
121 public List<PConstraint> getConsSecondary() {
122 return consSecondary;
123 }
124
125
126
127 public boolean isTrivial() {
128 return getPrimary().equals(getSecondary());
129 }
130
131 public boolean isSubsumption() {
132 return consPrimary.containsAll(consSecondary) || consSecondary.containsAll(consPrimary);
133 }
134
135 public boolean isCheckOnly() {
136 return varPrimary.containsAll(varSecondary) || varSecondary.containsAll(varPrimary);
137 }
138
139 public boolean isDescartes() {
140 return Collections.disjoint(varPrimary, varSecondary);
141 }
142
143 private Boolean heath;
144
145 // it is a Heath-join iff common variables functionally determine either all primary or all secondary variables
146 public boolean isHeath() {
147 if (heath == null) {
148 Set<PConstraint> union = Stream.concat(
149 primary.getAllEnforcedConstraints().stream(),
150 secondary.getAllEnforcedConstraints().stream()
151 ).collect(Collectors.toSet());
152 Map<Set<PVariable>, Set<PVariable>> dependencies =
153 analyzer.getFunctionalDependencies(union, false);
154 // does varCommon determine either varPrimary or varSecondary?
155 Set<PVariable> varCommonClosure = FunctionalDependencyHelper.closureOf(varCommon, dependencies);
156
157 heath = varCommonClosure.containsAll(varPrimary) || varCommonClosure.containsAll(varSecondary);
158 }
159 return heath;
160 }
161
162}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java
new file mode 100644
index 00000000..0ea7c1d9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction.quasitree;
11
12import java.util.Comparator;
13
14import tools.refinery.viatra.runtime.rete.util.Options;
15import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent;
16
17/**
18 * @author Gabor Bergmann
19 *
20 */
21public class JoinOrderingHeuristics implements Comparator<JoinCandidate> {
22
23 @Override
24 public int compare(JoinCandidate jc1, JoinCandidate jc2) {
25 return new OrderingCompareAgent<JoinCandidate>(jc1, jc2) {
26 @Override
27 protected void doCompare() {
28 swallowBoolean(true && consider(preferTrue(a.isTrivial(), b.isTrivial()))
29 && consider(preferTrue(a.isSubsumption(), b.isSubsumption()))
30 && consider(preferTrue(a.isCheckOnly(), b.isCheckOnly()))
31 && consider(
32 Options.functionalDependencyOption == Options.FunctionalDependencyOption.OFF ?
33 dontCare() :
34 preferTrue(a.isHeath(), b.isHeath())
35 )
36 && consider(preferFalse(a.isDescartes(), b.isDescartes()))
37
38 // TODO main heuristic decisions
39
40 // tie breaking
41 && consider(preferLess(a.getConsPrimary(), b.getConsPrimary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR))
42 && consider(preferLess(a.getConsSecondary(), b.getConsSecondary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR))
43 && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b))));
44 }
45 }.compare();
46
47 }
48
49} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java
new file mode 100644
index 00000000..9b814376
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java
@@ -0,0 +1,205 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.construction.quasitree;
11
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.LinkedHashSet;
15import java.util.List;
16import java.util.Set;
17
18import org.apache.log4j.Logger;
19import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
20import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
21import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
22import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
23import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
24import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy;
25import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
26import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory;
27import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper;
28import tools.refinery.viatra.runtime.matchers.planning.operations.PApply;
29import tools.refinery.viatra.runtime.matchers.planning.operations.PEnumerate;
30import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
31import tools.refinery.viatra.runtime.matchers.planning.operations.PStart;
32import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint;
33import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
34import tools.refinery.viatra.runtime.matchers.psystem.PBody;
35import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
36import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
37import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
38import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException;
39import tools.refinery.viatra.runtime.rete.util.ReteHintOptions;
40
41/**
42 * Layout ideas: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=398763
43 *
44 * @author Gabor Bergmann
45 *
46 */
47public class QuasiTreeLayout implements IQueryPlannerStrategy {
48
49 private IQueryBackendHintProvider hintProvider;
50 private IQueryBackendContext backendContext;
51 private QueryAnalyzer queryAnalyzer;
52
53 public QuasiTreeLayout(IQueryBackendContext backendContext) {
54 this(backendContext, backendContext.getHintProvider());
55 }
56
57 public QuasiTreeLayout(IQueryBackendContext backendContext, IQueryBackendHintProvider hintProvider) {
58 this.backendContext = backendContext;
59 this.hintProvider = hintProvider;
60 queryAnalyzer = backendContext.getQueryAnalyzer();
61 }
62
63 @Override
64 public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context) {
65 return new Scaffold(pSystem, logger, context).run();
66 }
67
68 public class Scaffold {
69 PBody pSystem;
70 PQuery query;
71 IQueryMetaContext context;
72 private QueryEvaluationHint hints;
73 //IOperationCompiler compiler;
74 //SubPlanProcessor planProcessor = new SubPlanProcessor();
75 SubPlanFactory planFactory;
76
77 Set<DeferredPConstraint> deferredConstraints = null;
78 Set<EnumerablePConstraint> enumerableConstraints = null;
79 Set<ConstantValue> constantConstraints = null;
80 Set<SubPlan> forefront = new LinkedHashSet<SubPlan>();
81 Logger logger;
82
83 Scaffold(PBody pSystem, Logger logger, /*IOperationCompiler compiler,*/ IQueryMetaContext context) {
84 this.pSystem = pSystem;
85 this.logger = logger;
86 this.context = context;
87 this.planFactory = new SubPlanFactory(pSystem);
88 query = pSystem.getPattern();
89 //this.compiler = compiler;
90 //planProcessor.setCompiler(compiler);
91
92 hints = hintProvider.getQueryEvaluationHint(query);
93 }
94
95 /**
96 * @throws ViatraQueryRuntimeException
97 */
98 public SubPlan run() {
99 try {
100 logger.debug(String.format(
101 "%s: patternbody build started for %s",
102 getClass().getSimpleName(),
103 query.getFullyQualifiedName()));
104
105 // PROCESS CONSTRAINTS
106 deferredConstraints = pSystem.getConstraintsOfType(DeferredPConstraint.class);
107 enumerableConstraints = pSystem.getConstraintsOfType(EnumerablePConstraint.class);
108 constantConstraints = pSystem.getConstraintsOfType(ConstantValue.class);
109
110 for (EnumerablePConstraint enumerable : enumerableConstraints) {
111 SubPlan plan = planFactory.createSubPlan(new PEnumerate(enumerable));
112 admitSubPlan(plan);
113 }
114 if (enumerableConstraints.isEmpty()) { // EXTREME CASE
115 SubPlan plan = planFactory.createSubPlan(new PStart());
116 admitSubPlan(plan);
117 }
118
119 // JOIN FOREFRONT PLANS WHILE POSSIBLE
120 while (forefront.size() > 1) {
121 // TODO QUASI-TREE TRIVIAL JOINS?
122
123 List<JoinCandidate> candidates = generateJoinCandidates();
124 JoinOrderingHeuristics ordering = new JoinOrderingHeuristics();
125 JoinCandidate selectedJoin = Collections.min(candidates, ordering);
126 doJoin(selectedJoin);
127 }
128 assert (forefront.size() == 1);
129
130 // PROJECT TO PARAMETERS
131 SubPlan preFinalPlan = forefront.iterator().next();
132 SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), preFinalPlan);
133
134 // FINAL CHECK, whether all exported variables are present + all constraint satisfied
135 BuildHelper.finalCheck(pSystem, finalPlan, context);
136 // TODO integrate the check above in SubPlan / POperation
137
138 logger.debug(String.format(
139 "%s: patternbody query plan concluded for %s as: %s",
140 getClass().getSimpleName(),
141 query.getFullyQualifiedName(),
142 finalPlan.toLongString()));
143 return finalPlan;
144 } catch (RetePatternBuildException ex) {
145 ex.setPatternDescription(query);
146 throw ex;
147 }
148 }
149
150 public List<JoinCandidate> generateJoinCandidates() {
151 List<JoinCandidate> candidates = new ArrayList<JoinCandidate>();
152 int bIndex = 0;
153 for (SubPlan b : forefront) {
154 int aIndex = 0;
155 for (SubPlan a : forefront) {
156 if (aIndex++ >= bIndex)
157 break;
158 candidates.add(new JoinCandidate(a, b, queryAnalyzer));
159 }
160 bIndex++;
161 }
162 return candidates;
163 }
164
165 private void admitSubPlan(SubPlan plan) {
166 // are there any unapplied constant filters that we can apply here?
167 if (ReteHintOptions.prioritizeConstantFiltering.getValueOrDefault(hints)) {
168 for (ConstantValue constantConstraint : constantConstraints) {
169 if (!plan.getAllEnforcedConstraints().contains(constantConstraint) &&
170 plan.getVisibleVariables().containsAll(constantConstraint.getAffectedVariables())) {
171 plan = planFactory.createSubPlan(new PApply(constantConstraint), plan);
172 }
173 }
174 }
175 // are there any variables that will not be needed anymore and are worth trimming?
176 // (check only if there are unenforced enumerables, so that there are still upcoming joins)
177// if (Options.planTrimOption != Options.PlanTrimOption.OFF &&
178// !plan.getAllEnforcedConstraints().containsAll(enumerableConstraints)) {
179 if (true) {
180 final SubPlan trimmed = BuildHelper.trimUnneccessaryVariables(
181 planFactory, plan, true, queryAnalyzer);
182 plan = trimmed;
183 }
184 // are there any checkable constraints?
185 for (DeferredPConstraint deferred : deferredConstraints) {
186 if (!plan.getAllEnforcedConstraints().contains(deferred)) {
187 if (deferred.isReadyAt(plan, context)) {
188 admitSubPlan(planFactory.createSubPlan(new PApply(deferred), plan));
189 return;
190 }
191 }
192 }
193 // if no checkable constraints and no unused variables
194 forefront.add(plan);
195 }
196
197 private void doJoin(JoinCandidate selectedJoin) {
198 forefront.remove(selectedJoin.getPrimary());
199 forefront.remove(selectedJoin.getSecondary());
200 admitSubPlan(selectedJoin.getJoinedPlan(planFactory));
201 }
202
203 }
204
205}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java
new file mode 100644
index 00000000..0b955922
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.construction.quasitree;
10
11import java.util.Comparator;
12
13import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
14import tools.refinery.viatra.runtime.rete.util.LexicographicComparator;
15
16/**
17 * Class providing comparators for breaking ties somewhat more deterministically.
18 * @author Bergmann Gabor
19 *
20 */
21public class TieBreaker {
22
23 private TieBreaker() {/*Utility class constructor*/}
24
25 public static final Comparator<PConstraint> CONSTRAINT_COMPARATOR = (arg0, arg1) -> arg0.getMonotonousID() - arg1.getMonotonousID();
26
27 public static final Comparator<Iterable<? extends PConstraint>> CONSTRAINT_LIST_COMPARATOR =
28 new LexicographicComparator<PConstraint>(CONSTRAINT_COMPARATOR);
29
30}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java
new file mode 100644
index 00000000..d32a0449
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import java.util.Iterator;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.util.Direction;
15import tools.refinery.viatra.runtime.rete.network.ReteContainer;
16import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
17import tools.refinery.viatra.runtime.rete.single.SingleInputNode;
18
19/**
20 * @author Bergmann Gabor
21 */
22public abstract class AbstractEvaluatorNode extends SingleInputNode implements IEvaluatorNode {
23
24 /**
25 * @since 1.5
26 */
27 protected EvaluatorCore core;
28
29
30 /**
31 * @since 1.5
32 */
33 public AbstractEvaluatorNode(ReteContainer reteContainer, EvaluatorCore core) {
34 super(reteContainer);
35 this.core = core;
36 core.init(this);
37 }
38
39 /**
40 * @since 1.5
41 */
42 @Override
43 public ReteContainer getReteContainer() {
44 return getContainer();
45 }
46
47 /**
48 * @since 1.5
49 */
50 @Override
51 public String prettyPrintTraceInfoPatternList() {
52 return getTraceInfoPatternsEnumerated();
53 }
54
55 /**
56 * @since 2.4
57 */
58 protected void propagateIterableUpdate(final Direction direction, final Iterable<Tuple> update, final Timestamp timestamp) {
59 final Iterator<Tuple> itr = update.iterator();
60 while (itr.hasNext()) {
61 propagateUpdate(direction, itr.next(), timestamp);
62 }
63 }
64
65}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java
new file mode 100644
index 00000000..c45c6048
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java
@@ -0,0 +1,180 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import java.util.Collections;
12import java.util.Iterator;
13import java.util.Map;
14import java.util.Set;
15
16import org.apache.log4j.Logger;
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.tuple.TupleValueProvider;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
22import tools.refinery.viatra.runtime.matchers.util.Sets;
23
24/**
25 * An instance of this class performs the evaluation of Java expressions.
26 *
27 * @author Bergmann Gabor
28 * @author Tamas Szabo
29 * @since 1.5
30 */
31public abstract class EvaluatorCore {
32
33 protected Logger logger;
34 protected IExpressionEvaluator evaluator;
35 /**
36 * @since 2.4
37 */
38 protected int sourceTupleWidth;
39 private Map<String, Integer> parameterPositions;
40 protected IQueryRuntimeContext runtimeContext;
41 protected IEvaluatorNode evaluatorNode;
42
43 public EvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator,
44 final Map<String, Integer> parameterPositions, final int sourceTupleWidth) {
45 this.logger = logger;
46 this.evaluator = evaluator;
47 this.parameterPositions = parameterPositions;
48 this.sourceTupleWidth = sourceTupleWidth;
49 }
50
51 public void init(final IEvaluatorNode evaluatorNode) {
52 this.evaluatorNode = evaluatorNode;
53 this.runtimeContext = evaluatorNode.getReteContainer().getNetwork().getEngine().getRuntimeContext();
54 }
55
56 /**
57 * @since 2.4
58 */
59 public abstract Iterable<Tuple> performEvaluation(final Tuple input);
60
61 protected abstract String evaluationKind();
62
63 public Object evaluateTerm(final Tuple input) {
64 // actual evaluation
65 Object result = null;
66 try {
67 final TupleValueProvider tupleParameters = new TupleValueProvider(runtimeContext.unwrapTuple(input),
68 parameterPositions);
69 result = evaluator.evaluateExpression(tupleParameters);
70 } catch (final Exception e) {
71 logger.warn(String.format(
72 "The incremental pattern matcher encountered an error during %s evaluation for pattern(s) %s over values %s. Error message: %s. (Developer note: %s in %s)",
73 evaluationKind(), evaluatorNode.prettyPrintTraceInfoPatternList(), prettyPrintTuple(input),
74 e.getMessage(), e.getClass().getSimpleName(), this.evaluatorNode), e);
75 result = errorResult();
76 }
77
78 return result;
79 }
80
81 protected String prettyPrintTuple(final Tuple tuple) {
82 return tuple.toString();
83 }
84
85 protected Object errorResult() {
86 return null;
87 }
88
89 public static class PredicateEvaluatorCore extends EvaluatorCore {
90
91 public PredicateEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator,
92 final Map<String, Integer> parameterPositions, final int sourceTupleWidth) {
93 super(logger, evaluator, parameterPositions, sourceTupleWidth);
94 }
95
96 @Override
97 public Iterable<Tuple> performEvaluation(final Tuple input) {
98 final Object result = evaluateTerm(input);
99 if (Boolean.TRUE.equals(result)) {
100 return Collections.singleton(input);
101 } else {
102 return null;
103 }
104 }
105
106 @Override
107 protected String evaluationKind() {
108 return "check()";
109 }
110
111 }
112
113 public static class FunctionEvaluatorCore extends EvaluatorCore {
114
115 /**
116 * @since 2.4
117 */
118 protected final boolean isUnwinding;
119
120 public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator,
121 final Map<String, Integer> parameterPositions, final int sourceTupleWidth) {
122 this(logger, evaluator, parameterPositions, sourceTupleWidth, false);
123 }
124
125 /**
126 * @since 2.4
127 */
128 public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator,
129 final Map<String, Integer> parameterPositions, final int sourceTupleWidth, final boolean isUnwinding) {
130 super(logger, evaluator, parameterPositions, sourceTupleWidth);
131 this.isUnwinding = isUnwinding;
132 }
133
134 @Override
135 public Iterable<Tuple> performEvaluation(final Tuple input) {
136 final Object result = evaluateTerm(input);
137 if (result != null) {
138 if (this.isUnwinding) {
139 final Set<?> resultAsSet = (result instanceof Set<?>) ? (Set<?>) result
140 : (result instanceof Iterable<?>) ? Sets.newSet((Iterable<?>) result) : null;
141
142 if (resultAsSet != null) {
143 return () -> {
144 final Iterator<?> wrapped = resultAsSet.iterator();
145 return new Iterator<Tuple>() {
146 @Override
147 public boolean hasNext() {
148 return wrapped.hasNext();
149 }
150
151 @Override
152 public Tuple next() {
153 final Object next = wrapped.next();
154 return Tuples.staticArityLeftInheritanceTupleOf(input,
155 runtimeContext.wrapElement(next));
156 }
157 };
158 };
159 } else {
160 throw new IllegalStateException(
161 "This is an unwinding evaluator, which expects the evaluation result to either be a set or an iterable, but it was "
162 + result);
163 }
164 } else {
165 return Collections.singleton(
166 Tuples.staticArityLeftInheritanceTupleOf(input, runtimeContext.wrapElement(result)));
167 }
168 } else {
169 return null;
170 }
171 }
172
173 @Override
174 protected String evaluationKind() {
175 return "eval" + (this.isUnwinding ? "Unwind" : "") + "()";
176 }
177
178 }
179
180}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java
new file mode 100644
index 00000000..177433ab
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import tools.refinery.viatra.runtime.rete.network.ReteContainer;
12
13/**
14 * This interface is required for the communication between the evaluation core end the evaluator node.
15 * @author Gabor Bergmann
16 * @since 1.5
17 */
18public interface IEvaluatorNode {
19
20 ReteContainer getReteContainer();
21
22 String prettyPrintTraceInfoPatternList();
23
24
25}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java
new file mode 100644
index 00000000..8928645c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java
@@ -0,0 +1,75 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Iterator;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
23
24/**
25 * @author Bergmann Gabor
26 *
27 */
28public class MemorylessEvaluatorNode extends AbstractEvaluatorNode {
29
30 /**
31 * @since 1.5
32 */
33 public MemorylessEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) {
34 super(reteContainer, core);
35 }
36
37 @Override
38 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
39 final Collection<Tuple> parentTuples = new ArrayList<Tuple>();
40 propagatePullInto(parentTuples, flush);
41 for (final Tuple parentTuple : parentTuples) {
42 final Iterable<Tuple> output = core.performEvaluation(parentTuple);
43 if (output != null) {
44 final Iterator<Tuple> itr = output.iterator();
45 while (itr.hasNext()) {
46 collector.add(itr.next());
47 }
48 }
49 }
50 }
51
52 @Override
53 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
54 final Map<Tuple, Timeline<Timestamp>> parentTuples = CollectionsFactory.createMap();
55 propagatePullIntoWithTimestamp(parentTuples, flush);
56 for (final Entry<Tuple, Timeline<Timestamp>> entry : parentTuples.entrySet()) {
57 final Iterable<Tuple> output = core.performEvaluation(entry.getKey());
58 if (output != null) {
59 final Iterator<Tuple> itr = output.iterator();
60 while (itr.hasNext()) {
61 collector.put(itr.next(), entry.getValue());
62 }
63 }
64 }
65 }
66
67 @Override
68 public void update(final Direction direction, final Tuple input, final Timestamp timestamp) {
69 final Iterable<Tuple> output = core.performEvaluation(input);
70 if (output != null) {
71 propagateIterableUpdate(direction, output, timestamp);
72 }
73 }
74
75}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java
new file mode 100644
index 00000000..40a20c4e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java
@@ -0,0 +1,311 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19import tools.refinery.viatra.runtime.matchers.util.Clearable;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.Direction;
22import tools.refinery.viatra.runtime.matchers.util.Signed;
23import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
25import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
26import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
27import tools.refinery.viatra.runtime.rete.network.ReteContainer;
28import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
29import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
30import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
31
32/**
33 * An evaluator node that caches the evaluation result. This node is also capable of caching the timestamps associated
34 * with the result tuples if it is used in recursive differential dataflow evaluation.
35 *
36 * @author Bergmann Gabor
37 * @author Tamas Szabo
38 */
39public class OutputCachingEvaluatorNode extends AbstractEvaluatorNode implements Clearable, ResumableNode {
40
41 /**
42 * @since 2.3
43 */
44 protected NetworkStructureChangeSensitiveLogic logic;
45
46 /**
47 * @since 2.4
48 */
49 protected Map<Tuple, Iterable<Tuple>> outputCache;
50
51 /**
52 * Maps input tuples to timestamps. It is wrong to map evaluation result to timestamps because the different input
53 * tuples may yield the same evaluation result. This field is null as long as this node is in a non-recursive group.
54 *
55 * @since 2.4
56 */
57 protected TimelyMemory<Timestamp> memory;
58
59 /**
60 * @since 2.4
61 */
62 protected CommunicationGroup group;
63
64 /**
65 * @since 1.5
66 */
67 public OutputCachingEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) {
68 super(reteContainer, core);
69 reteContainer.registerClearable(this);
70 this.outputCache = CollectionsFactory.createMap();
71 this.logic = createLogic();
72 }
73
74 @Override
75 public CommunicationGroup getCurrentGroup() {
76 return this.group;
77 }
78
79 @Override
80 public void setCurrentGroup(final CommunicationGroup group) {
81 this.group = group;
82 }
83
84 @Override
85 public void networkStructureChanged() {
86 super.networkStructureChanged();
87 this.logic = createLogic();
88 }
89
90 @Override
91 public void clear() {
92 this.outputCache.clear();
93 if (this.memory != null) {
94 this.memory.clear();
95 }
96 }
97
98 /**
99 * @since 2.3
100 */
101 protected NetworkStructureChangeSensitiveLogic createLogic() {
102 if (this.reteContainer.isTimelyEvaluation()
103 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
104 if (this.memory == null) {
105 this.memory = new TimelyMemory<Timestamp>(reteContainer.isTimelyEvaluation() && reteContainer
106 .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL);
107 }
108 return TIMELY;
109 } else {
110 return TIMELESS;
111 }
112 }
113
114 @Override
115 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
116 this.logic.pullInto(collector, flush);
117 }
118
119 @Override
120 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
121 this.logic.pullIntoWithTimeline(collector, flush);
122 }
123
124 @Override
125 public void update(final Direction direction, final Tuple input, final Timestamp timestamp) {
126 this.logic.update(direction, input, timestamp);
127 }
128
129 /**
130 * @since 2.4
131 */
132 @Override
133 public Timestamp getResumableTimestamp() {
134 if (this.memory == null) {
135 return null;
136 } else {
137 return this.memory.getResumableTimestamp();
138 }
139 }
140
141 /**
142 * @since 2.4
143 */
144 @Override
145 public void resumeAt(final Timestamp timestamp) {
146 this.logic.resumeAt(timestamp);
147 }
148
149 /**
150 * @since 2.3
151 */
152 protected static abstract class NetworkStructureChangeSensitiveLogic {
153
154 /**
155 * @since 2.4
156 */
157 public abstract void update(final Direction direction, final Tuple input, final Timestamp timestamp);
158
159 public abstract void pullInto(final Collection<Tuple> collector, final boolean flush);
160
161 /**
162 * @since 2.4
163 */
164 public abstract void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush);
165
166 /**
167 * @since 2.4
168 */
169 public abstract void resumeAt(final Timestamp timestamp);
170
171 }
172
173 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
174
175 @Override
176 public void resumeAt(final Timestamp timestamp) {
177 // there is nothing to resume in the timeless case because we do not even care about timestamps
178 }
179
180 @Override
181 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
182 throw new UnsupportedOperationException();
183 }
184
185 @Override
186 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
187 for (final Iterable<Tuple> output : outputCache.values()) {
188 if (output != NORESULT) {
189 final Iterator<Tuple> itr = output.iterator();
190 while (itr.hasNext()) {
191 collector.add(itr.next());
192 }
193 }
194 }
195 }
196
197 @Override
198 public void update(final Direction direction, final Tuple input, final Timestamp timestamp) {
199 if (direction == Direction.INSERT) {
200 final Iterable<Tuple> output = core.performEvaluation(input);
201 if (output != null) {
202 final Iterable<Tuple> previous = outputCache.put(input, output);
203 if (previous != null) {
204 throw new IllegalStateException(
205 String.format("Duplicate insertion of tuple %s into node %s", input, this));
206 }
207 propagateIterableUpdate(direction, output, timestamp);
208 }
209 } else {
210 final Iterable<Tuple> output = outputCache.remove(input);
211 if (output != null) {
212 // may be null if no result was yielded
213 propagateIterableUpdate(direction, output, timestamp);
214 }
215 }
216 }
217 };
218
219 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
220
221 @Override
222 public void resumeAt(final Timestamp timestamp) {
223 final Map<Tuple, Diff<Timestamp>> diffMap = memory.resumeAt(timestamp);
224
225 for (final Entry<Tuple, Diff<Timestamp>> entry : diffMap.entrySet()) {
226 final Tuple input = entry.getKey();
227 final Iterable<Tuple> output = outputCache.get(input);
228 if (output != NORESULT) {
229 for (final Signed<Timestamp> signed : entry.getValue()) {
230 propagateIterableUpdate(signed.getDirection(), output, signed.getPayload());
231 }
232 }
233
234 if (memory.get(input) == null) {
235 outputCache.remove(input);
236 }
237 }
238
239 final Timestamp nextTimestamp = memory.getResumableTimestamp();
240 if (nextTimestamp != null) {
241 group.notifyHasMessage(mailbox, nextTimestamp);
242 }
243 }
244
245 @Override
246 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
247 for (final Entry<Tuple, Timeline<Timestamp>> entry : memory.asMap().entrySet()) {
248 final Tuple input = entry.getKey();
249 final Iterable<Tuple> output = outputCache.get(input);
250 if (output != NORESULT) {
251 final Timeline<Timestamp> timestamp = entry.getValue();
252 final Iterator<Tuple> itr = output.iterator();
253 while (itr.hasNext()) {
254 collector.put(itr.next(), timestamp);
255 }
256 }
257 }
258 }
259
260 @Override
261 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
262 TIMELESS.pullInto(collector, flush);
263 }
264
265 @Override
266 public void update(final Direction direction, final Tuple input, final Timestamp timestamp) {
267 if (direction == Direction.INSERT) {
268 Iterable<Tuple> output = outputCache.get(input);
269 if (output == null) {
270 output = core.performEvaluation(input);
271 if (output == null) {
272 // the evaluation result is really null
273 output = NORESULT;
274 }
275 outputCache.put(input, output);
276 }
277 final Diff<Timestamp> diff = memory.put(input, timestamp);
278 if (output != NORESULT) {
279 for (final Signed<Timestamp> signed : diff) {
280 propagateIterableUpdate(signed.getDirection(), output, signed.getPayload());
281 }
282 }
283 } else {
284 final Iterable<Tuple> output = outputCache.get(input);
285 final Diff<Timestamp> diff = memory.remove(input, timestamp);
286 if (memory.get(input) == null) {
287 outputCache.remove(input);
288 }
289 if (output != NORESULT) {
290 for (final Signed<Timestamp> signed : diff) {
291 propagateIterableUpdate(signed.getDirection(), output, signed.getPayload());
292 }
293 }
294 }
295 }
296 };
297
298 /**
299 * This field is used to represent the "null" evaluation result. This is an optimization used in the timely case
300 * where the same tuple may be inserted multiple times with different timestamps. This way, we can also cache if
301 * something evaluated to null (instead of just forgetting about the previously computed result), thus avoiding the
302 * need to re-run a potentially expensive evaluation.
303 */
304 private static final Iterable<Tuple> NORESULT = Collections
305 .singleton(Tuples.staticArityFlatTupleOf(NoResult.INSTANCE));
306
307 private enum NoResult {
308 INSTANCE
309 }
310
311}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java
new file mode 100644
index 00000000..68d277e8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java
@@ -0,0 +1,183 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2022, Tamas Szabo, GitHub
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.eval;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.List;
14import java.util.Map;
15import java.util.Map.Entry;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator;
19import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.RelationEvaluation;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
21import tools.refinery.viatra.runtime.matchers.util.Clearable;
22import tools.refinery.viatra.runtime.matchers.util.Direction;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
25import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver;
26import tools.refinery.viatra.runtime.rete.network.ProductionNode;
27import tools.refinery.viatra.runtime.rete.network.Receiver;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.StandardNode;
30import tools.refinery.viatra.runtime.rete.network.Supplier;
31import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
32import tools.refinery.viatra.runtime.rete.single.AbstractUniquenessEnforcerNode;
33
34/**
35 * A node that operates in batch-style (see {@link Receiver#doesProcessUpdatesInBatch()} and evaluates arbitrary Java
36 * logic represented by an {@link IRelationEvaluator} on the input relations. This is the backing computation node of a
37 * {@link RelationEvaluation} constraint.
38 *
39 * @author Tamas Szabo
40 * @since 2.8
41 */
42public class RelationEvaluatorNode extends StandardNode implements Supplier, Clearable {
43
44 private final IRelationEvaluator evaluator;
45 private Set<Tuple> cachedOutputs;
46 private Supplier[] inputSuppliers;
47 private BatchingReceiver[] inputReceivers;
48
49 public RelationEvaluatorNode(final ReteContainer container, final IRelationEvaluator evaluator) {
50 super(container);
51 this.evaluator = evaluator;
52 this.reteContainer.registerClearable(this);
53 }
54
55 @Override
56 public void clear() {
57 this.cachedOutputs.clear();
58 }
59
60 public void connectToParents(final List<Supplier> inputSuppliers) {
61 this.inputSuppliers = new Supplier[inputSuppliers.size()];
62 this.inputReceivers = new BatchingReceiver[inputSuppliers.size()];
63
64 final List<Integer> inputArities = evaluator.getInputArities();
65
66 if (inputArities.size() != inputSuppliers.size()) {
67 throw new IllegalStateException(evaluator.toString() + " expects " + inputArities.size()
68 + " inputs, but got " + inputSuppliers.size() + " input(s)!");
69 }
70
71 for (int i = 0; i < inputSuppliers.size(); i++) {
72 final int currentExpectedInputArity = inputArities.get(i);
73 final Supplier inputSupplier = inputSuppliers.get(i);
74 // it is expected that the supplier is a production node because
75 // the corresponding constraint itself accepts a list of PQuery
76 if (!(inputSupplier instanceof ProductionNode)) {
77 throw new IllegalStateException(
78 evaluator.toString() + " expects each one of its suppliers to be instances of "
79 + ProductionNode.class.getSimpleName() + " but got an instance of "
80 + inputSupplier.getClass().getSimpleName() + "!");
81 }
82 final int currentActualInputArity = ((ProductionNode) inputSupplier).getPosMapping().size();
83 if (currentActualInputArity != currentExpectedInputArity) {
84 throw new IllegalStateException(
85 evaluator.toString() + " expects input arity " + currentExpectedInputArity + " at position " + i
86 + " but got " + currentActualInputArity + "!");
87 }
88 final BatchingReceiver inputReceiver = new BatchingReceiver((ProductionNode) inputSupplier,
89 this.reteContainer);
90 this.inputSuppliers[i] = inputSupplier;
91 this.inputReceivers[i] = inputReceiver;
92 this.reteContainer.connectAndSynchronize(inputSupplier, inputReceiver);
93 reteContainer.getCommunicationTracker().registerDependency(inputReceiver, this);
94 }
95
96 // initialize the output relation
97 final List<Set<Tuple>> inputSets = new ArrayList<Set<Tuple>>();
98 for (final BatchingReceiver inputReceiver : this.inputReceivers) {
99 inputSets.add(inputReceiver.getTuples());
100 }
101 this.cachedOutputs = evaluateRelation(inputSets);
102 }
103
104 @Override
105 public void networkStructureChanged() {
106 if (this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
107 throw new IllegalStateException(this.toString() + " cannot be used in recursive evaluation!");
108 }
109 super.networkStructureChanged();
110 }
111
112 @Override
113 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
114 collector.addAll(this.cachedOutputs);
115 }
116
117 @Override
118 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
119 final Timeline<Timestamp> timeline = Timelines.createFrom(Timestamp.ZERO);
120 for (final Tuple output : this.cachedOutputs) {
121 collector.put(output, timeline);
122 }
123 }
124
125 private Set<Tuple> evaluateRelation(final List<Set<Tuple>> inputs) {
126 try {
127 return this.evaluator.evaluateRelation(inputs);
128 } catch (final Exception e) {
129 throw new IllegalStateException("Exception during the evaluation of " + this.evaluator.toString() + "!", e);
130 }
131 }
132
133 private void batchUpdateCompleted() {
134 final List<Set<Tuple>> inputSets = new ArrayList<Set<Tuple>>();
135 for (final BatchingReceiver inputReceiver : this.inputReceivers) {
136 inputSets.add(inputReceiver.getTuples());
137 }
138 final Set<Tuple> newOutputs = evaluateRelation(inputSets);
139 for (final Tuple tuple : newOutputs) {
140 if (this.cachedOutputs != null && this.cachedOutputs.remove(tuple)) {
141 // already known tuple - do nothing
142 } else {
143 // newly inserted tuple
144 propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO);
145 }
146 }
147 if (this.cachedOutputs != null) {
148 for (final Tuple tuple : this.cachedOutputs) {
149 // lost tuple
150 propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO);
151 }
152 }
153 this.cachedOutputs = newOutputs;
154 }
155
156 public class BatchingReceiver extends SimpleReceiver {
157 private final ProductionNode source;
158
159 private BatchingReceiver(final ProductionNode source, final ReteContainer container) {
160 super(container);
161 this.source = source;
162 }
163
164 private Set<Tuple> getTuples() {
165 return ((AbstractUniquenessEnforcerNode) this.source).getTuples();
166 }
167
168 @Override
169 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
170 throw new UnsupportedOperationException("This receiver only supports batch-style operation!");
171 }
172
173 @Override
174 public void batchUpdate(final Collection<Entry<Tuple, Integer>> updates, final Timestamp timestamp) {
175 assert Timestamp.ZERO.equals(timestamp);
176 // there is nothing to do here because the source production node has already updated itself
177 // the only thing we need to do is to issue the callback
178 RelationEvaluatorNode.this.batchUpdateCompleted();
179 }
180
181 }
182
183}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java
new file mode 100644
index 00000000..6306a482
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.index;
10
11import java.lang.ref.WeakReference;
12
13import tools.refinery.viatra.runtime.rete.network.Node;
14
15public abstract class DefaultIndexerListener implements IndexerListener {
16
17 WeakReference<Node> owner;
18
19 public DefaultIndexerListener(Node owner) {
20 this.owner = new WeakReference<Node>(owner);
21 }
22
23 @Override
24 public Node getOwner() {
25 return owner.get();
26 }
27
28}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java
new file mode 100644
index 00000000..170ac460
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java
@@ -0,0 +1,348 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
21import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
22import tools.refinery.viatra.runtime.rete.network.Receiver;
23import tools.refinery.viatra.runtime.rete.network.ReteContainer;
24import tools.refinery.viatra.runtime.rete.network.StandardNode;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.communication.Timestamp.AllZeroMap;
27import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand;
28import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
29import tools.refinery.viatra.runtime.rete.util.Options;
30
31/**
32 * Abstract superclass for nodes with two inputs that are matched against each other.
33 *
34 * @author Gabor Bergmann
35 */
36public abstract class DualInputNode extends StandardNode implements NetworkStructureChangeSensitiveNode {
37
38 /**
39 * @since 2.3
40 */
41 protected NetworkStructureChangeSensitiveLogic logic;
42
43 public IterableIndexer getPrimarySlot() {
44 return primarySlot;
45 }
46
47 public Indexer getSecondarySlot() {
48 return secondarySlot;
49 }
50
51 /**
52 * @author Gabor Bergmann
53 *
54 */
55 public enum Side {
56 PRIMARY, SECONDARY, BOTH;
57
58 public Side opposite() {
59 switch (this) {
60 case PRIMARY:
61 return SECONDARY;
62 case SECONDARY:
63 return PRIMARY;
64 case BOTH:
65 return BOTH;
66 default:
67 return BOTH;
68 }
69 }
70 }
71
72 /**
73 * Holds the primary input slot of this node.
74 */
75 protected IterableIndexer primarySlot;
76
77 /**
78 * Holds the secondary input slot of this node.
79 */
80 protected Indexer secondarySlot;
81
82 /**
83 * Optional complementer mask
84 */
85 protected TupleMask complementerSecondaryMask;
86
87 /**
88 * true if the primary and secondary slots coincide
89 */
90 protected boolean coincidence;
91
92 /**
93 * @param reteContainer
94 */
95 public DualInputNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) {
96 super(reteContainer);
97 this.complementerSecondaryMask = complementerSecondaryMask;
98 this.indexerGroupCache = CollectionsFactory.createMap();
99 this.refreshIndexerGroupCache();
100 }
101
102 /**
103 * Should be called only once, when node is initialized.
104 */
105 public void connectToIndexers(final IterableIndexer primarySlot, final Indexer secondarySlot) {
106 this.primarySlot = primarySlot;
107 this.secondarySlot = secondarySlot;
108
109 reteContainer.getCommunicationTracker().registerDependency(primarySlot, this);
110 reteContainer.getCommunicationTracker().registerDependency(secondarySlot, this);
111
112 // attach listeners
113 // if there is syncing, do this after the flush done for pulling, but before syncing updates
114 coincidence = primarySlot.equals(secondarySlot);
115
116 if (!coincidence) { // regular case
117 primarySlot.attachListener(new DefaultIndexerListener(this) {
118 @Override
119 public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement,
120 final Tuple signature, final boolean change, final Timestamp timestamp) {
121 DualInputNode.this.logic.notifyUpdate(Side.PRIMARY, direction, updateElement, signature, change,
122 timestamp);
123 }
124
125 @Override
126 public String toString() {
127 return "primary@" + DualInputNode.this;
128 }
129 });
130 secondarySlot.attachListener(new DefaultIndexerListener(this) {
131 public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement,
132 final Tuple signature, final boolean change, final Timestamp timestamp) {
133 DualInputNode.this.logic.notifyUpdate(Side.SECONDARY, direction, updateElement, signature, change,
134 timestamp);
135 }
136
137 @Override
138 public String toString() {
139 return "secondary@" + DualInputNode.this;
140 }
141 });
142 } else { // if the two slots are the same, updates have to be handled carefully
143 primarySlot.attachListener(new DefaultIndexerListener(this) {
144 public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement,
145 final Tuple signature, final boolean change, final Timestamp timestamp) {
146 DualInputNode.this.logic.notifyUpdate(Side.BOTH, direction, updateElement, signature, change,
147 timestamp);
148 }
149
150 @Override
151 public String toString() {
152 return "both@" + DualInputNode.this;
153 }
154 });
155 }
156
157 for (final Receiver receiver : getReceivers()) {
158 this.reteContainer.getDelayedCommandQueue()
159 .add(new DelayedConnectCommand(this, receiver, this.reteContainer));
160 }
161
162 // Given that connectToIndexers registers new dependencies, the networkStructureChanged() method will be called
163 // by the CommunicationTracker, and the implementation of that method in turn will call refreshIndexerGroupCache() anyway.
164 this.refreshIndexerGroupCache();
165 }
166
167 /**
168 * Helper: retrieves all stored substitutions from the opposite side memory.
169 *
170 * @return the collection of opposite substitutions if any, or null if none
171 */
172 protected Collection<Tuple> retrieveOpposites(final Side side, final Tuple signature) {
173 return getSlot(side.opposite()).get(signature);
174 }
175
176 /**
177 * @since 2.3
178 */
179 protected NetworkStructureChangeSensitiveLogic createLogic() {
180 if (this.reteContainer.isTimelyEvaluation()
181 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
182 return createTimelyLogic();
183 } else {
184 return createTimelessLogic();
185 }
186 }
187
188 /**
189 * Helper: unifies a left and right partial matching.
190 */
191 protected Tuple unify(final Tuple left, final Tuple right) {
192 return complementerSecondaryMask.combine(left, right, Options.enableInheritance, true);
193 }
194
195 @Override
196 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
197 this.logic.pullInto(collector, flush);
198 }
199
200 @Override
201 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
202 this.logic.pullIntoWithTimeline(collector, flush);
203 }
204
205 /**
206 * Helper: unifies a substitution from the specified side with another substitution from the other side.
207 */
208 protected Tuple unify(final Side side, final Tuple ps, final Tuple opposite) {
209 switch (side) {
210 case PRIMARY:
211 return unify(ps, opposite);
212 case SECONDARY:
213 return unify(opposite, ps);
214 case BOTH:
215 return unify(ps, opposite);
216 default:
217 return null;
218 }
219 }
220
221 /**
222 * Simulates the behavior of the node for calibration purposes only.
223 */
224 public abstract Tuple calibrate(final Tuple primary, final Tuple secondary);
225
226 /**
227 * @param complementerSecondaryMask
228 * the complementerSecondaryMask to set
229 */
230 public void setComplementerSecondaryMask(final TupleMask complementerSecondaryMask) {
231 this.complementerSecondaryMask = complementerSecondaryMask;
232 }
233
234 /**
235 * Retrieves the slot corresponding to the specified side.
236 */
237 protected Indexer getSlot(final Side side) {
238 if (side == Side.SECONDARY) {
239 return secondarySlot;
240 } else {
241 return primarySlot;
242 }
243 }
244
245 @Override
246 public void assignTraceInfo(final TraceInfo traceInfo) {
247 super.assignTraceInfo(traceInfo);
248 if (traceInfo.propagateToIndexerParent()) {
249 if (primarySlot != null) {
250 primarySlot.acceptPropagatedTraceInfo(traceInfo);
251 }
252 if (secondarySlot != null) {
253 secondarySlot.acceptPropagatedTraceInfo(traceInfo);
254 }
255 }
256 }
257
258 @Override
259 public void networkStructureChanged() {
260 super.networkStructureChanged();
261 this.logic = createLogic();
262 this.refreshIndexerGroupCache();
263 }
264
265 /**
266 * @since 2.3
267 */
268 protected abstract NetworkStructureChangeSensitiveLogic createTimelyLogic();
269
270 /**
271 * @since 2.3
272 */
273 protected abstract NetworkStructureChangeSensitiveLogic createTimelessLogic();
274
275 /**
276 * This map caches the result of a CommunicationTracker.areInSameGroup(indexer, this) call. It does that for both
277 * the primary and secondary slots. This way we can avoid the lookup in the getWithTimestamp call for each tuple.
278 * The cache needs to be maintained when the network structure changes.
279 * @since 2.3
280 */
281 protected Map<Indexer, Boolean> indexerGroupCache;
282
283 /**
284 * @since 2.3
285 */
286 protected void refreshIndexerGroupCache() {
287 this.indexerGroupCache.clear();
288 if (this.primarySlot != null) {
289 this.indexerGroupCache.put(this.primarySlot,
290 this.reteContainer.getCommunicationTracker().areInSameGroup(this.primarySlot, this));
291 }
292 if (this.secondarySlot != null) {
293 this.indexerGroupCache.put(this.secondarySlot,
294 this.reteContainer.getCommunicationTracker().areInSameGroup(this.secondarySlot, this));
295 }
296 }
297
298 /**
299 * @since 2.4
300 */
301 protected Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature, final Indexer indexer) {
302 if (this.indexerGroupCache.get(indexer)) {
303 // recursive timely case
304 return indexer.getTimeline(signature);
305 } else {
306 // the indexer is in a different group, treat all of its tuples as they would have timestamp 0
307 final Collection<Tuple> tuples = indexer.get(signature);
308 if (tuples == null) {
309 return null;
310 } else {
311 return new AllZeroMap<Tuple>((Set<Tuple>) tuples);
312 }
313 }
314 }
315
316 /**
317 * @since 2.3
318 */
319 protected static abstract class NetworkStructureChangeSensitiveLogic {
320
321 /**
322 * Abstract handler for update event.
323 *
324 * @param side
325 * The side on which the event occurred.
326 * @param direction
327 * The direction of the update.
328 * @param updateElement
329 * The partial matching that is inserted.
330 * @param signature
331 * Masked signature of updateElement.
332 * @param change
333 * Indicates whether this is/was the first/last instance of this signature in this slot.
334 * @since 2.4
335 */
336 public abstract void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement,
337 final Tuple signature, final boolean change, final Timestamp timestamp);
338
339 public abstract void pullInto(final Collection<Tuple> collector, final boolean flush);
340
341 /**
342 * @since 2.4
343 */
344 public abstract void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush);
345
346 }
347
348}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java
new file mode 100644
index 00000000..275ff638
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java
@@ -0,0 +1,199 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.matchers.util.Signed;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.network.ReteContainer;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22/**
23 * Propagates all substitutions arriving at the PRIMARY slot if and only if (a matching substitution on the SECONDARY is
24 * present) xor (NEGATIVE).
25 *
26 * The negative parameter specifies whether this node checks for existence or non-existence.
27 * <p>
28 * It is mandatory in differential dataflow evaluation that the secondary parent is in an upstream dependency component
29 * (so that every secondary tuple comes with zero timestamp).
30 *
31 * @author Gabor Bergmann
32 */
33public class ExistenceNode extends DualInputNode {
34
35 protected boolean negative;
36
37 /**
38 * @param reteContainer
39 * @param negative
40 * if false, act as existence checker, otherwise a nonexistence-checker
41 */
42 public ExistenceNode(final ReteContainer reteContainer, final boolean negative) {
43 super(reteContainer, null);
44 this.negative = negative;
45 this.logic = createLogic();
46 }
47
48 @Override
49 public Tuple calibrate(final Tuple primary, final Tuple secondary) {
50 return primary;
51 }
52
53 @Override
54 public void networkStructureChanged() {
55 if (this.reteContainer.isTimelyEvaluation() && this.secondarySlot != null
56 && this.reteContainer.getCommunicationTracker().areInSameGroup(this, this.secondarySlot)) {
57 throw new IllegalStateException("Secondary parent must be in an upstream dependency component!");
58 }
59 super.networkStructureChanged();
60 }
61
62 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
63
64 @Override
65 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
66 throw new UnsupportedOperationException();
67 }
68
69 @Override
70 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
71 if (primarySlot == null || secondarySlot == null) {
72 return;
73 }
74 if (flush) {
75 reteContainer.flushUpdates();
76 }
77
78 for (final Tuple signature : primarySlot.getSignatures()) {
79 // primaries can not be null due to the contract of IterableIndex.getSignatures()
80 final Collection<Tuple> primaries = primarySlot.get(signature);
81 final Collection<Tuple> opposites = secondarySlot.get(signature);
82 if ((opposites != null) ^ negative) {
83 collector.addAll(primaries);
84 }
85 }
86 }
87
88 @Override
89 public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement,
90 final Tuple signature, final boolean change, final Timestamp timestamp) {
91 // in the default case, all timestamps must be zero
92 assert Timestamp.ZERO.equals(timestamp);
93
94 switch (side) {
95 case PRIMARY:
96 if ((retrieveOpposites(side, signature) != null) ^ negative) {
97 propagateUpdate(direction, updateElement, timestamp);
98 }
99 break;
100 case SECONDARY:
101 if (change) {
102 final Collection<Tuple> opposites = retrieveOpposites(side, signature);
103 if (opposites != null) {
104 for (final Tuple opposite : opposites) {
105 propagateUpdate((negative ? direction.opposite() : direction), opposite, timestamp);
106 }
107 }
108 }
109 break;
110 case BOTH:
111 // in case the slots coincide,
112 // negative --> always empty
113 // !positive --> identity
114 if (!negative) {
115 propagateUpdate(direction, updateElement, timestamp);
116 }
117 break;
118 }
119 }
120 };
121
122 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
123
124 @Override
125 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
126 if (primarySlot == null || secondarySlot == null) {
127 return;
128 }
129 if (flush) {
130 reteContainer.flushUpdates();
131 }
132
133 for (final Tuple signature : primarySlot.getSignatures()) {
134 // primaries can not be null due to the contract of IterableIndex.getSignatures()
135 final Map<Tuple, Timeline<Timestamp>> primaries = getTimeline(signature, primarySlot);
136 // see contract: secondary must be in an upstream SCC
137 final Collection<Tuple> opposites = secondarySlot.get(signature);
138 if ((opposites != null) ^ negative) {
139 for (final Tuple primary : primaries.keySet()) {
140 collector.put(primary, primaries.get(primary));
141 }
142 }
143 }
144 }
145
146 @Override
147 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
148 ExistenceNode.this.TIMELESS.pullInto(collector, flush);
149 }
150
151 @Override
152 public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement,
153 final Tuple signature, final boolean change, final Timestamp timestamp) {
154 switch (side) {
155 case PRIMARY: {
156 final Collection<Tuple> opposites = secondarySlot.get(signature);
157 if ((opposites != null) ^ negative) {
158 propagateUpdate(direction, updateElement, timestamp);
159 }
160 break;
161 }
162 case SECONDARY: {
163 final Map<Tuple, Timeline<Timestamp>> opposites = primarySlot.getTimeline(signature);
164 if (change) {
165 if (opposites != null) {
166 for (final Tuple opposite : opposites.keySet()) {
167 for (final Signed<Timestamp> oppositeSigned : opposites.get(opposite).asChangeSequence()) {
168 final Direction product = direction.multiply(oppositeSigned.getDirection());
169 propagateUpdate((negative ? product.opposite() : product), opposite,
170 oppositeSigned.getPayload());
171 }
172 }
173 }
174 }
175 break;
176 }
177 case BOTH:
178 // in case the slots coincide,
179 // negative --> always empty
180 // positive --> identity
181 if (!negative) {
182 propagateUpdate(direction, updateElement, timestamp);
183 }
184 break;
185 }
186 }
187 };
188
189 @Override
190 protected NetworkStructureChangeSensitiveLogic createTimelessLogic() {
191 return this.TIMELESS;
192 }
193
194 @Override
195 protected NetworkStructureChangeSensitiveLogic createTimelyLogic() {
196 return this.TIMELY;
197 }
198
199}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java
new file mode 100644
index 00000000..3de10def
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Iterator;
14import java.util.Map;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
20import tools.refinery.viatra.runtime.rete.network.Receiver;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
23
24/**
25 * A generic Indexer capable of indexing along any valid TupleMask. Does not keep track of parents, because will not
26 * ever pull parents.
27 *
28 * @author Gabor Bergmann
29 *
30 */
31public class GenericProjectionIndexer extends IndexerWithMemory implements ProjectionIndexer {
32
33 public GenericProjectionIndexer(ReteContainer reteContainer, TupleMask mask) {
34 super(reteContainer, mask);
35 }
36
37 @Override
38 protected void update(Direction direction, Tuple updateElement, Tuple signature, boolean change,
39 Timestamp timestamp) {
40 propagate(direction, updateElement, signature, change, timestamp);
41 }
42
43 @Override
44 public Collection<Tuple> get(Tuple signature) {
45 return memory.get(signature);
46 }
47
48 @Override
49 public Map<Tuple, Timeline<Timestamp>> getTimeline(Tuple signature) {
50 return memory.getWithTimeline(signature);
51 }
52
53 @Override
54 public Iterator<Tuple> iterator() {
55 return memory.iterator();
56 }
57
58 @Override
59 public Iterable<Tuple> getSignatures() {
60 return memory.getSignatures();
61 }
62
63 /**
64 * @since 2.0
65 */
66 @Override
67 public int getBucketCount() {
68 return memory.getKeysetSize();
69 }
70
71 @Override
72 public Receiver getActiveNode() {
73 return this;
74 }
75
76}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java
new file mode 100644
index 00000000..6c158f2c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2012 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Iterator;
15import java.util.List;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.rete.network.Node;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.Supplier;
23import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
24
25/**
26 * Defines an abstract trivial indexer that identically projects the contents of some stateful node, and can therefore
27 * save space. Can only exist in connection with a stateful store, and must be operated by another node (the active
28 * node). Do not attach parents directly!
29 *
30 * @author Gabor Bergmann
31 * @noimplement Rely on the provided implementations
32 * @noreference Use only via standard Node and Indexer interfaces
33 * @noinstantiate This class is not intended to be instantiated by clients.
34 */
35public abstract class IdentityIndexer extends SpecializedProjectionIndexer {
36
37 protected abstract Collection<Tuple> getTuples();
38
39 public IdentityIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent,
40 Node activeNode, List<ListenerSubscription> sharedSubscriptionList) {
41 super(reteContainer, TupleMask.identity(tupleWidth), parent, activeNode, sharedSubscriptionList);
42 }
43
44 @Override
45 public Collection<Tuple> get(Tuple signature) {
46 if (contains(signature)) {
47 return Collections.singleton(signature);
48 } else
49 return null;
50 }
51
52 protected boolean contains(Tuple signature) {
53 return getTuples().contains(signature);
54 }
55
56 @Override
57 public Collection<Tuple> getSignatures() {
58 return getTuples();
59 }
60
61 @Override
62 public int getBucketCount() {
63 return getTuples().size();
64 }
65
66 @Override
67 public Iterator<Tuple> iterator() {
68 return getTuples().iterator();
69 }
70
71 @Override
72 public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, Timestamp timestamp) {
73 listener.notifyIndexerUpdate(direction, updateElement, updateElement, true, timestamp);
74 }
75
76} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java
new file mode 100644
index 00000000..fc9d7781
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java
@@ -0,0 +1,71 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.Node;
19import tools.refinery.viatra.runtime.rete.network.Supplier;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22/**
23 * A node that indexes incoming Tuples by their signatures as specified by a TupleMask. Notifies listeners about such
24 * update events through the IndexerListener.
25 *
26 * Signature tuples are created by transforming the update tuples using the mask. Tuples stored with the same signature
27 * are grouped together. The group or a reduction thereof is retrievable.
28 *
29 * @author Gabor Bergmann
30 */
31public interface Indexer extends Node {
32 /**
33 * @return the mask by which the contents are indexed.
34 */
35 public TupleMask getMask();
36
37 /**
38 * @return the node whose contents are indexed.
39 */
40 public Supplier getParent();
41
42 /**
43 * @return all stored tuples that conform to the specified signature, null if there are none such. CONTRACT: do not
44 * modify!
45 */
46 public Collection<Tuple> get(Tuple signature);
47
48 /**
49 * @since 2.4
50 */
51 default public Map<Tuple, Timeline<Timestamp>> getTimeline(Tuple signature) {
52 throw new UnsupportedOperationException();
53 }
54
55 /**
56 * This indexer will be updated whenever a Rete update is sent to the active node (or an equivalent time slot
57 * allotted to it). The active node is typically the indexer itself, but it can be a different node such as its
58 * parent.
59 *
60 * @return the active node that operates this indexer
61 */
62 public Node getActiveNode();
63
64
65 public Collection<IndexerListener> getListeners();
66
67 public void attachListener(IndexerListener listener);
68
69 public void detachListener(IndexerListener listener);
70
71}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java
new file mode 100644
index 00000000..f52b6a06
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.util.Direction;
14import tools.refinery.viatra.runtime.rete.network.Node;
15import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
16
17/**
18 * A listener for update events concerning an Indexer.
19 *
20 * @author Gabor Bergmann
21 *
22 */
23public interface IndexerListener {
24 /**
25 * Notifies recipient that the indexer has just received an update. Contract: indexer already reflects the updated
26 * state.
27 *
28 * @param direction
29 * the direction of the update.
30 * @param updateElement
31 * the tuple that was updated.
32 * @param signature
33 * the signature of the tuple according to the indexer's mask.
34 * @param change
35 * whether this was the first inserted / last revoked update element with this particular signature.
36 * @since 2.4
37 */
38 void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp);
39
40 Node getOwner();
41}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java
new file mode 100644
index 00000000..a31562e9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java
@@ -0,0 +1,284 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
22import tools.refinery.viatra.runtime.matchers.util.Direction;
23import tools.refinery.viatra.runtime.matchers.util.Signed;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
25import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
26import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
27import tools.refinery.viatra.runtime.rete.network.Receiver;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.Supplier;
30import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
31import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
32import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
33import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
34import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
35import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
36
37/**
38 * @author Gabor Bergmann
39 * @author Tamas Szabo
40 */
41public abstract class IndexerWithMemory extends StandardIndexer
42 implements Receiver, NetworkStructureChangeSensitiveNode, ResumableNode {
43
44 protected MaskedTupleMemory<Timestamp> memory;
45
46 /**
47 * @since 2.3
48 */
49 protected NetworkStructureChangeSensitiveLogic logic;
50
51 /**
52 * @since 1.6
53 */
54 protected final Mailbox mailbox;
55
56 /**
57 * @since 2.4
58 */
59 protected CommunicationGroup group;
60
61 public IndexerWithMemory(final ReteContainer reteContainer, final TupleMask mask) {
62 super(reteContainer, mask);
63 final boolean isTimely = reteContainer.isTimelyEvaluation()
64 && reteContainer.getCommunicationTracker().isInRecursiveGroup(this);
65 memory = MaskedTupleMemory.create(mask, MemoryType.SETS, this, isTimely, isTimely && reteContainer
66 .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL);
67 reteContainer.registerClearable(memory);
68 mailbox = instantiateMailbox();
69 reteContainer.registerClearable(mailbox);
70 this.logic = createLogic();
71 }
72
73 @Override
74 public CommunicationGroup getCurrentGroup() {
75 return this.group;
76 }
77
78 @Override
79 public void setCurrentGroup(final CommunicationGroup group) {
80 this.group = group;
81 }
82
83 @Override
84 public void networkStructureChanged() {
85 super.networkStructureChanged();
86 final boolean wasTimely = this.memory.isTimely();
87 final boolean isTimely = this.reteContainer.isTimelyEvaluation()
88 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this);
89 if (wasTimely != isTimely) {
90 final MaskedTupleMemory<Timestamp> newMemory = MaskedTupleMemory.create(mask, MemoryType.SETS, this,
91 isTimely, isTimely && reteContainer.getTimelyConfiguration()
92 .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL);
93 newMemory.initializeWith(this.memory, Timestamp.ZERO);
94 memory.clear();
95 memory = newMemory;
96 }
97 this.logic = createLogic();
98 }
99
100 /**
101 * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own
102 * mailbox implementation.
103 *
104 * @return the mailbox
105 * @since 2.0
106 */
107 protected Mailbox instantiateMailbox() {
108 if (this.reteContainer.isTimelyEvaluation()) {
109 return new TimelyMailbox(this, this.reteContainer);
110 } else {
111 return new BehaviorChangingMailbox(this, this.reteContainer);
112 }
113 }
114
115 @Override
116 public Mailbox getMailbox() {
117 return this.mailbox;
118 }
119
120 /**
121 * @since 2.0
122 */
123 public MaskedTupleMemory<Timestamp> getMemory() {
124 return memory;
125 }
126
127 @Override
128 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
129 this.logic.update(direction, updateElement, timestamp);
130 }
131
132 /**
133 * Refined version of update
134 *
135 * @since 2.4
136 */
137 protected abstract void update(final Direction direction, final Tuple updateElement, final Tuple signature,
138 final boolean change, final Timestamp timestamp);
139
140 @Override
141 public void appendParent(final Supplier supplier) {
142 if (parent == null) {
143 parent = supplier;
144 } else {
145 throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent
146 + ") and cannot connect to additional parent (" + supplier + "). ");
147 }
148 }
149
150 @Override
151 public void removeParent(final Supplier supplier) {
152 if (parent == supplier) {
153 parent = null;
154 } else {
155 throw new IllegalArgumentException(
156 "Illegal RETE edge removal: the parent of " + this + " is not " + supplier);
157 }
158 }
159
160 /**
161 * @since 2.4
162 */
163 @Override
164 public Collection<Supplier> getParents() {
165 return Collections.singleton(parent);
166 }
167
168 /**
169 * @since 2.4
170 */
171 @Override
172 public void resumeAt(final Timestamp timestamp) {
173 this.logic.resumeAt(timestamp);
174 }
175
176 /**
177 * @since 2.4
178 */
179 @Override
180 public Timestamp getResumableTimestamp() {
181 return this.memory.getResumableTimestamp();
182 }
183
184 /**
185 * @since 2.3
186 */
187 protected static abstract class NetworkStructureChangeSensitiveLogic {
188
189 /**
190 * @since 2.4
191 */
192 public abstract void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp);
193
194 /**
195 * @since 2.4
196 */
197 public abstract void resumeAt(final Timestamp timestamp);
198
199 }
200
201 /**
202 * @since 2.3
203 */
204 protected NetworkStructureChangeSensitiveLogic createLogic() {
205 if (this.reteContainer.isTimelyEvaluation()
206 && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
207 return TIMELY;
208 } else {
209 return TIMELESS;
210 }
211 }
212
213 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
214
215 @Override
216 public void resumeAt(final Timestamp timestamp) {
217 final Iterable<Tuple> signatures = memory.getResumableSignatures();
218
219 final Map<Tuple, Boolean> wasPresent = CollectionsFactory.createMap();
220 for (final Tuple signature : signatures) {
221 wasPresent.put(signature, memory.isPresentAtInfinity(signature));
222 }
223
224 final Map<Tuple, Map<Tuple, Diff<Timestamp>>> signatureMap = memory.resumeAt(timestamp);
225
226 for (final Entry<Tuple, Map<Tuple, Diff<Timestamp>>> outerEntry : signatureMap.entrySet()) {
227 final Tuple signature = outerEntry.getKey();
228 final Map<Tuple, Diff<Timestamp>> diffMap = outerEntry.getValue();
229 final boolean isPresent = memory.isPresentAtInfinity(signature);
230 // only send out a potential true value the first time for a given signature, then set it to false
231 boolean change = wasPresent.get(signature) ^ isPresent;
232
233 for (final Entry<Tuple, Diff<Timestamp>> innerEntry : diffMap.entrySet()) {
234 final Tuple tuple = innerEntry.getKey();
235 final Diff<Timestamp> diffs = innerEntry.getValue();
236 for (final Signed<Timestamp> signed : diffs) {
237 IndexerWithMemory.this.update(signed.getDirection(), tuple, signature, change,
238 signed.getPayload());
239 }
240 // change is a signature-wise flag, so it is ok to "try" to signal it for the first tuple only
241 change = false;
242 }
243 }
244
245 final Timestamp nextTimestamp = memory.getResumableTimestamp();
246 if (nextTimestamp != null) {
247 group.notifyHasMessage(mailbox, nextTimestamp);
248 }
249 }
250
251 @Override
252 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
253 final Tuple signature = mask.transform(update);
254 final boolean wasPresent = memory.isPresentAtInfinity(signature);
255 final Diff<Timestamp> resultDiff = direction == Direction.INSERT
256 ? memory.addWithTimestamp(update, signature, timestamp)
257 : memory.removeWithTimestamp(update, signature, timestamp);
258 final boolean isPresent = memory.isPresentAtInfinity(signature);
259 final boolean change = wasPresent ^ isPresent;
260 for (final Signed<Timestamp> signed : resultDiff) {
261 IndexerWithMemory.this.update(signed.getDirection(), update, signature, change, signed.getPayload());
262 }
263 }
264
265 };
266
267 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
268
269 @Override
270 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
271 final Tuple signature = mask.transform(update);
272 final boolean change = direction == Direction.INSERT ? memory.add(update, signature)
273 : memory.remove(update, signature);
274 IndexerWithMemory.this.update(direction, update, signature, change, timestamp);
275 }
276
277 @Override
278 public void resumeAt(final Timestamp timestamp) {
279 // there is nothing to resume in the timeless case because we do not even care about timestamps
280 }
281
282 };
283
284} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java
new file mode 100644
index 00000000..d6f8ef05
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13
14/**
15 * An indexer that allows the iteration of all retrievable tuple groups (or reduced groups).
16 *
17 * @author Gabor Bergmann
18 *
19 */
20public interface IterableIndexer extends Indexer, Iterable<Tuple> {
21
22 /**
23 * A view consisting of exactly those signatures whose tuple group is not empty
24 * @since 2.0
25 */
26 public Iterable<Tuple> getSignatures();
27
28 /**
29 * @return the number of signatures whose tuple group is not empty
30 * @since 2.0
31 */
32 public int getBucketCount();
33
34}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java
new file mode 100644
index 00000000..9a6a0de9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java
@@ -0,0 +1,193 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.matchers.util.Signed;
19import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
20import tools.refinery.viatra.runtime.rete.network.ReteContainer;
21import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
22
23/**
24 * @author Gabor Bergmann
25 *
26 */
27public class JoinNode extends DualInputNode {
28
29 public JoinNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) {
30 super(reteContainer, complementerSecondaryMask);
31 this.logic = createLogic();
32 }
33
34 @Override
35 public Tuple calibrate(final Tuple primary, final Tuple secondary) {
36 return unify(primary, secondary);
37 }
38
39 private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() {
40
41 @Override
42 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
43 throw new UnsupportedOperationException();
44 }
45
46 @Override
47 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
48 if (primarySlot == null || secondarySlot == null) {
49 return;
50 }
51
52 if (flush) {
53 reteContainer.flushUpdates();
54 }
55
56 for (final Tuple signature : primarySlot.getSignatures()) {
57 // primaries can not be null due to the contract of IterableIndex.getSignatures()
58 final Collection<Tuple> primaries = primarySlot.get(signature);
59 final Collection<Tuple> opposites = secondarySlot.get(signature);
60 if (opposites != null) {
61 for (final Tuple primary : primaries) {
62 for (final Tuple opposite : opposites) {
63 collector.add(unify(primary, opposite));
64 }
65 }
66 }
67 }
68 }
69
70 @Override
71 public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement,
72 final Tuple signature, final boolean change, final Timestamp timestamp) {
73 // in the default case, all timestamps must be zero
74 assert Timestamp.ZERO.equals(timestamp);
75
76 final Collection<Tuple> opposites = retrieveOpposites(side, signature);
77
78 if (!coincidence) {
79 if (opposites != null) {
80 for (final Tuple opposite : opposites) {
81 propagateUpdate(direction, unify(side, updateElement, opposite), timestamp);
82 }
83 }
84 } else {
85 // compensate for coincidence of slots - this is the case when an Indexer is joined with itself
86 if (opposites != null) {
87 for (final Tuple opposite : opposites) {
88 if (opposite.equals(updateElement)) {
89 // handle self-joins of a single tuple separately
90 continue;
91 }
92 propagateUpdate(direction, unify(opposite, updateElement), timestamp);
93 propagateUpdate(direction, unify(updateElement, opposite), timestamp);
94 }
95 }
96
97 // handle self-joins here
98 propagateUpdate(direction, unify(updateElement, updateElement), timestamp);
99 }
100 }
101 };
102
103 private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() {
104
105 @Override
106 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
107 if (primarySlot == null || secondarySlot == null) {
108 return;
109 }
110
111 if (flush) {
112 reteContainer.flushUpdates();
113 }
114
115 for (final Tuple signature : primarySlot.getSignatures()) {
116 // primaries can not be null due to the contract of IterableIndex.getSignatures()
117 final Map<Tuple, Timeline<Timestamp>> primaries = getTimeline(signature, primarySlot);
118 final Map<Tuple, Timeline<Timestamp>> opposites = getTimeline(signature, secondarySlot);
119 if (opposites != null) {
120 for (final Tuple primary : primaries.keySet()) {
121 for (final Tuple opposite : opposites.keySet()) {
122 final Timeline<Timestamp> primaryTimeline = primaries.get(primary);
123 final Timeline<Timestamp> oppositeTimeline = opposites.get(opposite);
124 final Timeline<Timestamp> mergedTimeline = primaryTimeline
125 .mergeMultiplicative(oppositeTimeline);
126 if (!mergedTimeline.isEmpty()) {
127 collector.put(unify(primary, opposite), mergedTimeline);
128 }
129 }
130 }
131 }
132 }
133 }
134
135 @Override
136 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
137 JoinNode.this.TIMELESS.pullInto(collector, flush);
138 }
139
140 @Override
141 public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement,
142 final Tuple signature, final boolean change, final Timestamp timestamp) {
143 final Indexer oppositeIndexer = getSlot(side.opposite());
144 final Map<Tuple, Timeline<Timestamp>> opposites = getTimeline(signature, oppositeIndexer);
145
146 if (!coincidence) {
147 if (opposites != null) {
148 for (final Tuple opposite : opposites.keySet()) {
149 final Tuple unifiedTuple = unify(side, updateElement, opposite);
150 for (final Signed<Timestamp> signed : opposites.get(opposite).asChangeSequence()) {
151 // TODO only consider signed timestamps that are greater or equal to timestamp
152 // plus compact the previous timestamps into at most one update
153 propagateUpdate(signed.getDirection().multiply(direction), unifiedTuple,
154 timestamp.max(signed.getPayload()));
155 }
156 }
157 }
158 } else {
159 // compensate for coincidence of slots - this is the case when an Indexer is joined with itself
160 if (opposites != null) {
161 for (final Tuple opposite : opposites.keySet()) {
162 if (opposite.equals(updateElement)) {
163 // handle self-joins of a single tuple separately
164 continue;
165 }
166 final Tuple u1 = unify(opposite, updateElement);
167 final Tuple u2 = unify(updateElement, opposite);
168 for (final Signed<Timestamp> oppositeSigned : opposites.get(opposite).asChangeSequence()) {
169 final Direction updateDirection = direction.multiply(oppositeSigned.getDirection());
170 final Timestamp updateTimestamp = timestamp.max(oppositeSigned.getPayload());
171 propagateUpdate(updateDirection, u1, updateTimestamp);
172 propagateUpdate(updateDirection, u2, updateTimestamp);
173 }
174 }
175 }
176
177 // handle self-join here
178 propagateUpdate(direction, unify(updateElement, updateElement), timestamp);
179 }
180 }
181 };
182
183 @Override
184 protected NetworkStructureChangeSensitiveLogic createTimelessLogic() {
185 return this.TIMELESS;
186 }
187
188 @Override
189 protected NetworkStructureChangeSensitiveLogic createTimelyLogic() {
190 return this.TIMELY;
191 }
192
193}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java
new file mode 100644
index 00000000..59b75c33
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.List;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.rete.network.Receiver;
17import tools.refinery.viatra.runtime.rete.network.ReteContainer;
18import tools.refinery.viatra.runtime.rete.network.Supplier;
19
20/**
21 * Defines a trivial indexer that identically projects the contents of a memory-equipped node, and can therefore save
22 * space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents
23 * directly!
24 *
25 * @noimplement Rely on the provided implementations
26 * @noreference Use only via standard Node and Indexer interfaces
27 * @noinstantiate This class is not intended to be instantiated by clients.
28 * @author Gabor Bergmann
29 */
30
31public class MemoryIdentityIndexer extends IdentityIndexer {
32
33 protected final Collection<Tuple> memory;
34
35 /**
36 * @param reteContainer
37 * @param tupleWidth
38 * the width of the tuples of memoryNode
39 * @param memory
40 * the memory whose contents are to be identity-indexed
41 * @param parent
42 * the parent node that owns the memory
43 */
44 public MemoryIdentityIndexer(ReteContainer reteContainer, int tupleWidth, Collection<Tuple> memory,
45 Supplier parent, Receiver activeNode, List<ListenerSubscription> sharedSubscriptionList) {
46 super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList);
47 this.memory = memory;
48 }
49
50 @Override
51 protected Collection<Tuple> getTuples() {
52 return this.memory;
53 }
54
55}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java
new file mode 100644
index 00000000..204fe433
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.List;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.rete.network.Receiver;
17import tools.refinery.viatra.runtime.rete.network.ReteContainer;
18import tools.refinery.viatra.runtime.rete.network.Supplier;
19
20/**
21 * Defines a trivial indexer that projects the contents of a memory-equipped node to the empty tuple, and can therefore
22 * save space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents
23 * directly!
24 *
25 * @author Gabor Bergmann
26 * @noimplement Rely on the provided implementations
27 * @noreference Use only via standard Node and Indexer interfaces
28 * @noinstantiate This class is not intended to be instantiated by clients.
29 */
30public class MemoryNullIndexer extends NullIndexer {
31
32 Collection<Tuple> memory;
33
34 /**
35 * @param reteContainer
36 * @param tupleWidth
37 * the width of the tuples of memoryNode
38 * @param memory
39 * the memory whose contents are to be null-indexed
40 * @param parent
41 * the parent node that owns the memory
42 */
43 public MemoryNullIndexer(ReteContainer reteContainer, int tupleWidth, Collection<Tuple> memory,
44 Supplier parent, Receiver activeNode, List<ListenerSubscription> sharedSubscriptionList) {
45 super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList);
46 this.memory = memory;
47 }
48
49 @Override
50 protected Collection<Tuple> getTuples() {
51 return this.memory;
52 }
53
54}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java
new file mode 100644
index 00000000..a875b29f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2012 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Iterator;
15import java.util.List;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
20import tools.refinery.viatra.runtime.matchers.util.Direction;
21import tools.refinery.viatra.runtime.rete.network.Node;
22import tools.refinery.viatra.runtime.rete.network.ReteContainer;
23import tools.refinery.viatra.runtime.rete.network.Supplier;
24import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
25
26/**
27 * Defines an abstract trivial indexer that projects the contents of some stateful node to the empty tuple, and can
28 * therefore save space. Can only exist in connection with a stateful store, and must be operated by another node (the
29 * active node). Do not attach parents directly!
30 *
31 * @author Gabor Bergmann
32 * @noimplement Rely on the provided implementations
33 * @noreference Use only via standard Node and Indexer interfaces
34 * @noinstantiate This class is not intended to be instantiated by clients.
35 */
36public abstract class NullIndexer extends SpecializedProjectionIndexer {
37
38 protected abstract Collection<Tuple> getTuples();
39
40 protected static final Tuple nullSignature = Tuples.staticArityFlatTupleOf();
41 protected static final Collection<Tuple> nullSingleton = Collections.singleton(nullSignature);
42 protected static final Collection<Tuple> emptySet = Collections.emptySet();
43
44 public NullIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent, Node activeNode,
45 List<ListenerSubscription> sharedSubscriptionList) {
46 super(reteContainer, TupleMask.linear(0, tupleWidth), parent, activeNode, sharedSubscriptionList);
47 }
48
49 @Override
50 public Collection<Tuple> get(Tuple signature) {
51 if (nullSignature.equals(signature))
52 return isEmpty() ? null : getTuples();
53 else
54 return null;
55 }
56
57 @Override
58 public Collection<Tuple> getSignatures() {
59 return isEmpty() ? emptySet : nullSingleton;
60 }
61
62 protected boolean isEmpty() {
63 return getTuples().isEmpty();
64 }
65
66 protected boolean isSingleElement() {
67 return getTuples().size() == 1;
68 }
69
70 @Override
71 public Iterator<Tuple> iterator() {
72 return getTuples().iterator();
73 }
74
75 @Override
76 public int getBucketCount() {
77 return getTuples().isEmpty() ? 0 : 1;
78 }
79
80 @Override
81 public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement,
82 Timestamp timestamp) {
83 boolean radical = (direction == Direction.DELETE && isEmpty())
84 || (direction == Direction.INSERT && isSingleElement());
85 listener.notifyIndexerUpdate(direction, updateElement, nullSignature, radical, timestamp);
86 }
87
88} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java
new file mode 100644
index 00000000..fef84bb1
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java
@@ -0,0 +1,47 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
16import tools.refinery.viatra.runtime.rete.network.ReteContainer;
17import tools.refinery.viatra.runtime.rete.network.Supplier;
18
19/**
20 * @author Gabor Bergmann Indexer whose lifetime last until the first get() DO NOT connect to nodes!
21 */
22public class OnetimeIndexer extends GenericProjectionIndexer {
23
24 public OnetimeIndexer(ReteContainer reteContainer, TupleMask mask) {
25 super(reteContainer, mask);
26 }
27
28 @Override
29 public Collection<Tuple> get(Tuple signature) {
30 if (tools.refinery.viatra.runtime.rete.util.Options.releaseOnetimeIndexers) {
31 reteContainer.unregisterClearable(memory);
32 reteContainer.unregisterNode(this);
33 }
34 return super.get(signature);
35 }
36
37 @Override
38 public void appendParent(Supplier supplier) {
39 throw new UnsupportedOperationException("onetime indexer cannot have parents");
40 }
41
42 @Override
43 public void attachListener(IndexerListener listener) {
44 throw new UnsupportedOperationException("onetime indexer cannot have listeners");
45 }
46
47}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java
new file mode 100644
index 00000000..58e593d9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12/**
13 * An iterable indexer that receives updates from a node, and groups received tuples intact, i.e. it does not reduce
14 * tuple groups.
15 *
16 * @author Gabor Bergmann
17 *
18 */
19public interface ProjectionIndexer extends IterableIndexer {
20
21}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java
new file mode 100644
index 00000000..9c647aa9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java
@@ -0,0 +1,176 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2012 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.ArrayList;
13import java.util.List;
14import java.util.Objects;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.rete.network.Node;
20import tools.refinery.viatra.runtime.rete.network.ReteContainer;
21import tools.refinery.viatra.runtime.rete.network.Supplier;
22import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
23import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
24
25/**
26 * A specialized projection indexer that can be memory-less (relying on an external source of information).
27 *
28 * <p>
29 * All specialized projection indexers of a single node will share the same listener list, so that notification order is
30 * maintained (see Bug 518434).
31 *
32 * @author Gabor Bergmann
33 * @noimplement Rely on the provided implementations
34 * @noreference Use only via standard Node and Indexer interfaces
35 * @noinstantiate This class is not intended to be instantiated by clients.
36 */
37public abstract class SpecializedProjectionIndexer extends StandardIndexer implements ProjectionIndexer {
38
39 protected Node activeNode;
40 protected List<ListenerSubscription> subscriptions;
41
42 /**
43 * @since 1.7
44 */
45 public SpecializedProjectionIndexer(final ReteContainer reteContainer, final TupleMask mask, final Supplier parent,
46 final Node activeNode, final List<ListenerSubscription> subscriptions) {
47 super(reteContainer, mask);
48 this.parent = parent;
49 this.activeNode = activeNode;
50 this.subscriptions = subscriptions;
51 }
52
53 public List<ListenerSubscription> getSubscriptions() {
54 return subscriptions;
55 }
56
57 @Override
58 public Node getActiveNode() {
59 return activeNode;
60 }
61
62 @Override
63 protected void propagate(final Direction direction, final Tuple updateElement, final Tuple signature,
64 final boolean change, final Timestamp timestamp) {
65 throw new UnsupportedOperationException();
66 }
67
68 @Override
69 public void attachListener(final IndexerListener listener) {
70 super.attachListener(listener);
71 final CommunicationTracker tracker = this.getCommunicationTracker();
72 final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener);
73 final ListenerSubscription subscription = new ListenerSubscription(this, proxy);
74 tracker.registerDependency(this, proxy.getOwner());
75 // See Bug 518434
76 // Must add to the first position, so that the later listeners are notified earlier.
77 // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite
78 // slot,
79 // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier,
80 // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation
81 subscriptions.add(0, subscription);
82 }
83
84 @Override
85 public void detachListener(final IndexerListener listener) {
86 final CommunicationTracker tracker = this.getCommunicationTracker();
87 // obtain the proxy before the super call would unregister the dependency
88 final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener);
89 super.detachListener(listener);
90 final ListenerSubscription subscription = new ListenerSubscription(this, proxy);
91 final boolean wasContained = subscriptions.remove(subscription);
92 assert wasContained;
93 tracker.unregisterDependency(this, proxy.getOwner());
94 }
95
96 @Override
97 public void networkStructureChanged() {
98 super.networkStructureChanged();
99 final List<ListenerSubscription> oldSubscriptions = new ArrayList<ListenerSubscription>();
100 oldSubscriptions.addAll(subscriptions);
101 subscriptions.clear();
102 for (final ListenerSubscription oldSubscription : oldSubscriptions) {
103 // there is no need to unregister and re-register the dependency between indexer and listener
104 // because the owner of the listener is the same (even if it is proxified)
105 final CommunicationTracker tracker = this.getCommunicationTracker();
106 // the subscriptions are shared, so we MUST reuse the indexer of the subscription instead of simply 'this'
107 final IndexerListener proxy = tracker.proxifyIndexerListener(oldSubscription.indexer, oldSubscription.listener);
108 final ListenerSubscription newSubscription = new ListenerSubscription(oldSubscription.indexer, proxy);
109 subscriptions.add(newSubscription);
110 }
111 }
112
113 /**
114 * @since 2.4
115 */
116 public abstract void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement,
117 Timestamp timestamp);
118
119 /**
120 * Infrastructure to share subscriptions between specialized indexers of the same parent node.
121 *
122 * @author Gabor Bergmann
123 * @since 1.7
124 */
125 public static class ListenerSubscription {
126 protected SpecializedProjectionIndexer indexer;
127 protected IndexerListener listener;
128
129 public ListenerSubscription(SpecializedProjectionIndexer indexer, IndexerListener listener) {
130 super();
131 this.indexer = indexer;
132 this.listener = listener;
133 }
134
135 /**
136 * @since 2.4
137 */
138 public SpecializedProjectionIndexer getIndexer() {
139 return indexer;
140 }
141
142 /**
143 * @since 2.4
144 */
145 public IndexerListener getListener() {
146 return listener;
147 }
148
149 /**
150 * Call this from parent node.
151 * @since 2.4
152 */
153 public void propagate(Direction direction, Tuple updateElement, Timestamp timestamp) {
154 indexer.propagateToListener(listener, direction, updateElement, timestamp);
155 }
156
157 @Override
158 public int hashCode() {
159 return Objects.hash(indexer, listener);
160 }
161
162 @Override
163 public boolean equals(Object obj) {
164 if (this == obj)
165 return true;
166 if (obj == null)
167 return false;
168 if (getClass() != obj.getClass())
169 return false;
170 ListenerSubscription other = (ListenerSubscription) obj;
171 return Objects.equals(listener, other.listener) && Objects.equals(indexer, other.indexer);
172 }
173
174 }
175
176}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java
new file mode 100644
index 00000000..9847a8dd
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.index;
11
12import java.util.Collection;
13import java.util.List;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.rete.network.BaseNode;
20import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.Supplier;
23import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
24import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
25
26/**
27 * An abstract standard implementation of the Indexer interface, providing common bookkeeping functionality.
28 *
29 * @author Gabor Bergmann
30 *
31 */
32public abstract class StandardIndexer extends BaseNode implements Indexer, NetworkStructureChangeSensitiveNode {
33
34 protected Supplier parent;
35 private final List<IndexerListener> originalListeners;
36 private final List<IndexerListener> proxyListeners;
37 protected TupleMask mask;
38
39 public StandardIndexer(ReteContainer reteContainer, TupleMask mask) {
40 super(reteContainer);
41 this.parent = null;
42 this.mask = mask;
43 this.originalListeners = CollectionsFactory.createObserverList();
44 this.proxyListeners = CollectionsFactory.createObserverList();
45 }
46
47 /**
48 * @since 2.4
49 */
50 protected void propagate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) {
51 for (IndexerListener listener : proxyListeners) {
52 listener.notifyIndexerUpdate(direction, updateElement, signature, change, timestamp);
53 }
54 }
55
56 @Override
57 public TupleMask getMask() {
58 return mask;
59 }
60
61 @Override
62 public Supplier getParent() {
63 return parent;
64 }
65
66 @Override
67 public void attachListener(IndexerListener listener) {
68 this.getCommunicationTracker().registerDependency(this, listener.getOwner());
69 // obtain the proxy after registering the dependency because then the proxy reflects the new SCC structure
70 final IndexerListener proxy = this.getCommunicationTracker().proxifyIndexerListener(this, listener);
71 // See Bug 518434
72 // Must add to the first position, so that the later listeners are notified earlier.
73 // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite slot,
74 // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier,
75 // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation
76 this.originalListeners.add(0, listener);
77 this.proxyListeners.add(0, proxy);
78 }
79
80 @Override
81 public void detachListener(IndexerListener listener) {
82 this.originalListeners.remove(listener);
83 IndexerListener listenerToRemove = null;
84 for (final IndexerListener proxyListener : this.proxyListeners) {
85 if (proxyListener.getOwner() == listener.getOwner()) {
86 listenerToRemove = proxyListener;
87 break;
88 }
89 }
90 assert listenerToRemove != null;
91 this.proxyListeners.remove(listenerToRemove);
92 this.getCommunicationTracker().unregisterDependency(this, listener.getOwner());
93 }
94
95 @Override
96 public void networkStructureChanged() {
97 this.proxyListeners.clear();
98 for (final IndexerListener original : this.originalListeners) {
99 this.proxyListeners.add(this.getCommunicationTracker().proxifyIndexerListener(this, original));
100 }
101 }
102
103 @Override
104 public Collection<IndexerListener> getListeners() {
105 return proxyListeners;
106 }
107
108 @Override
109 public ReteContainer getContainer() {
110 return reteContainer;
111 }
112
113 @Override
114 protected String toStringCore() {
115 return super.toStringCore() + "(" + parent + "/" + mask + ")";
116 }
117
118 @Override
119 public void assignTraceInfo(TraceInfo traceInfo) {
120 super.assignTraceInfo(traceInfo);
121 if (traceInfo.propagateFromIndexerToSupplierParent())
122 if (parent != null)
123 parent.acceptPropagatedTraceInfo(traceInfo);
124 }
125
126
127}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java
new file mode 100644
index 00000000..77202004
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java
@@ -0,0 +1,121 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.index;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.rete.itc.alg.incscc.IncSCCAlg;
17import tools.refinery.viatra.runtime.matchers.tuple.MaskedTuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
21import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
22import tools.refinery.viatra.runtime.matchers.util.Direction;
23import tools.refinery.viatra.runtime.rete.network.Receiver;
24import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode;
25
26// UNFINISHED, not used yet
27public class TransitiveClosureNodeIndexer extends StandardIndexer implements IterableIndexer {
28 private TransitiveClosureNode tcNode;
29 private IncSCCAlg<Object> tcAlg;
30 private Collection<Tuple> emptySet;
31
32 public TransitiveClosureNodeIndexer(TupleMask mask, IncSCCAlg<Object> tcAlg, TransitiveClosureNode tcNode) {
33 super(tcNode.getContainer(), mask);
34 this.tcAlg = tcAlg;
35 this.tcNode = tcNode;
36 this.emptySet = Collections.emptySet();
37 this.parent = tcNode;
38 }
39
40 @Override
41 public Collection<Tuple> get(Tuple signature) {
42 if (signature.getSize() == mask.sourceWidth) {
43 if (mask.indices.length == 0) {
44 // mask ()/2
45 return getSignatures();
46 } else if (mask.indices.length == 1) {
47 Set<Tuple> retSet = CollectionsFactory.createSet();
48
49 // mask (0)/2
50 if (mask.indices[0] == 0) {
51 Object source = signature.get(0);
52 for (Object target : tcAlg.getAllReachableTargets(source)) {
53 retSet.add(Tuples.staticArityFlatTupleOf(source, target));
54 }
55 return retSet;
56 }
57 // mask (1)/2
58 if (mask.indices[0] == 1) {
59 Object target = signature.get(1);
60 for (Object source : tcAlg.getAllReachableSources(target)) {
61 retSet.add(Tuples.staticArityFlatTupleOf(source, target));
62 }
63 return retSet;
64 }
65 } else {
66 // mask (0,1)/2
67 if (mask.indices[0] == 0 && mask.indices[1] == 1) {
68 Object source = signature.get(0);
69 Object target = signature.get(1);
70 Tuple singleton = Tuples.staticArityFlatTupleOf(source, target);
71 return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet);
72 }
73 // mask (1,0)/2
74 if (mask.indices[0] == 1 && mask.indices[1] == 0) {
75 Object source = signature.get(1);
76 Object target = signature.get(0);
77 Tuple singleton = Tuples.staticArityFlatTupleOf(source, target);
78 return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet);
79 }
80 }
81 }
82 return null;
83 }
84
85 @Override
86 public int getBucketCount() {
87 throw new UnsupportedOperationException();
88 }
89
90 @Override
91 public Collection<Tuple> getSignatures() {
92 return asTupleCollection(tcAlg.getTcRelation());
93 }
94
95 @Override
96 public Iterator<Tuple> iterator() {
97 return asTupleCollection(tcAlg.getTcRelation()).iterator();
98 }
99
100 private Collection<Tuple> asTupleCollection(
101 Collection<tools.refinery.viatra.runtime.rete.itc.alg.misc.Tuple<Object>> tuples) {
102 Set<Tuple> retSet = CollectionsFactory.createSet();
103 for (tools.refinery.viatra.runtime.rete.itc.alg.misc.Tuple<Object> tuple : tuples) {
104 retSet.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget()));
105 }
106 return retSet;
107 }
108
109 /**
110 * @since 2.4
111 */
112 public void propagate(Direction direction, Tuple updateElement, boolean change) {
113 propagate(direction, updateElement, new MaskedTuple(updateElement, mask), change, null);
114 }
115
116 @Override
117 public Receiver getActiveNode() {
118 return tcNode;
119 }
120
121}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java
new file mode 100644
index 00000000..4319ee29
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.index.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.List;
14import java.util.Map;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.index.IdentityIndexer;
20import tools.refinery.viatra.runtime.rete.network.Receiver;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.Supplier;
23import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
24
25public class TimelyMemoryIdentityIndexer extends IdentityIndexer {
26
27 protected final TimelyMemory<Timestamp> memory;
28
29 public TimelyMemoryIdentityIndexer(final ReteContainer reteContainer, final int tupleWidth,
30 final TimelyMemory<Timestamp> memory, final Supplier parent, final Receiver activeNode,
31 final List<ListenerSubscription> sharedSubscriptionList) {
32 super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList);
33 this.memory = memory;
34 }
35
36 @Override
37 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
38 final Timeline<Timestamp> timestamp = this.memory.get(signature);
39 if (timestamp != null) {
40 return Collections.singletonMap(signature, timestamp);
41 } else {
42 return null;
43 }
44 }
45
46 @Override
47 protected Collection<Tuple> getTuples() {
48 return this.memory.getTuplesAtInfinity();
49 }
50
51}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java
new file mode 100644
index 00000000..0386b006
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.index.timely;
10
11import java.util.Collection;
12import java.util.List;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.index.NullIndexer;
19import tools.refinery.viatra.runtime.rete.network.Receiver;
20import tools.refinery.viatra.runtime.rete.network.ReteContainer;
21import tools.refinery.viatra.runtime.rete.network.Supplier;
22import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
23
24public class TimelyMemoryNullIndexer extends NullIndexer {
25
26 protected final TimelyMemory<Timestamp> memory;
27
28 public TimelyMemoryNullIndexer(final ReteContainer reteContainer, final int tupleWidth,
29 final TimelyMemory<Timestamp> memory, final Supplier parent,
30 final Receiver activeNode, final List<ListenerSubscription> sharedSubscriptionList) {
31 super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList);
32 this.memory = memory;
33 }
34
35 @Override
36 public Map<Tuple, Timeline<Timestamp>> getTimeline(final Tuple signature) {
37 if (nullSignature.equals(signature)) {
38 return isEmpty() ? null : this.memory.asMap();
39 } else {
40 return null;
41 }
42 }
43
44 @Override
45 protected Collection<Tuple> getTuples() {
46 return this.memory.getTuplesAtInfinity();
47 }
48
49}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingAlg.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingAlg.java
new file mode 100644
index 00000000..199b44b1
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingAlg.java
@@ -0,0 +1,226 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.counting;
11
12import java.util.List;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.rete.itc.alg.misc.DFSPathFinder;
16import tools.refinery.viatra.runtime.rete.itc.alg.misc.IGraphPathFinder;
17import tools.refinery.viatra.runtime.rete.itc.alg.misc.ITcRelation;
18import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
19import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalWrapper;
20import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
21import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphObserver;
22import tools.refinery.viatra.runtime.rete.itc.igraph.ITcDataSource;
23import tools.refinery.viatra.runtime.rete.itc.igraph.ITcObserver;
24import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
25import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
26
27/**
28 * This class is the optimized implementation of the Counting algorithm.
29 *
30 * @author Tamas Szabo
31 *
32 * @param <V>
33 * the type parameter of the nodes in the graph data source
34 */
35public class CountingAlg<V> implements IGraphObserver<V>, ITcDataSource<V> {
36
37 private CountingTcRelation<V> tc = null;
38 private IBiDirectionalGraphDataSource<V> gds = null;
39 private List<ITcObserver<V>> observers;
40
41 /**
42 * Constructs a new Counting algorithm and initializes the transitive closure relation with the given graph data
43 * source. Attach itself on the graph data source as an observer.
44 *
45 * @param gds
46 * the graph data source instance
47 */
48 public CountingAlg(IGraphDataSource<V> gds) {
49
50 if (gds instanceof IBiDirectionalGraphDataSource<?>) {
51 this.gds = (IBiDirectionalGraphDataSource<V>) gds;
52 } else {
53 this.gds = new IBiDirectionalWrapper<V>(gds);
54 }
55
56 observers = CollectionsFactory.<ITcObserver<V>>createObserverList();
57 tc = new CountingTcRelation<V>(true);
58
59 initTc();
60 gds.attachObserver(this);
61 }
62
63 /**
64 * Initializes the transitive closure relation.
65 */
66 private void initTc() {
67 this.setTcRelation(CountingTcRelation.createFrom(gds));
68 }
69
70 @Override
71 public void edgeInserted(V source, V target) {
72 if (!source.equals(target)) {
73 deriveTc(source, target, true);
74 }
75 }
76
77 @Override
78 public void edgeDeleted(V source, V target) {
79 if (!source.equals(target)) {
80 deriveTc(source, target, false);
81 }
82 }
83
84 @Override
85 public void nodeInserted(V n) {
86
87 }
88
89 @Override
90 public void nodeDeleted(V n) {
91 this.tc.deleteTupleEnd(n);
92 }
93
94 /**
95 * Derives the transitive closure relation when an edge is inserted or deleted.
96 *
97 * @param source
98 * the source of the edge
99 * @param target
100 * the target of the edge
101 * @param dCount
102 * the value is -1 if an edge was deleted and +1 if an edge was inserted
103 */
104 private void deriveTc(V source, V target, boolean isInsertion) {
105
106 // if (dCount == 1 && isReachable(target, source)) {
107 // System.out.println("The graph contains cycle with (" + source + ","+ target + ") edge!");
108 // }
109
110 CountingTcRelation<V> dtc = new CountingTcRelation<V>(false);
111 Set<V> tupEnds = null;
112
113 // 1. d(tc(x,y)) :- d(l(x,y))
114 if (tc.updateTuple(source, target, isInsertion)) {
115 dtc.updateTuple(source, target, true /* deltas implicitly have the same sign as isInsertion*/);
116 notifyTcObservers(source, target, isInsertion);
117 }
118
119 // 2. d(tc(x,y)) :- d(l(x,z)) & tc(z,y)
120 tupEnds = tc.getTupleEnds(target);
121 if (tupEnds != null) {
122 for (V tupEnd : tupEnds) {
123 if (!tupEnd.equals(source)) {
124 if (tc.updateTuple(source, tupEnd, isInsertion)) {
125 dtc.updateTuple(source, tupEnd, true /* deltas implicitly have the same sign as isInsertion*/);
126 notifyTcObservers(source, tupEnd, isInsertion);
127 }
128 }
129 }
130 }
131
132 // 3. d(tc(x,y)) :- lv(x,z) & d(tc(z,y))
133 CountingTcRelation<V> newTuples = dtc;
134 CountingTcRelation<V> tmp = null;
135 dtc = new CountingTcRelation<V>(false);
136
137 IMemoryView<V> nodes = null;
138
139 while (!newTuples.isEmpty()) {
140
141 tmp = dtc;
142 dtc = newTuples;
143 newTuples = tmp;
144 newTuples.clear();
145
146 for (V tS : dtc.getTupleStarts()) {
147 nodes = gds.getSourceNodes(tS);
148 for (V nS : nodes.distinctValues()) {
149 int count = nodes.getCount(nS);
150 for (int i = 0; i < count; i++) {
151 tupEnds = dtc.getTupleEnds(tS);
152 if (tupEnds != null) {
153 for (V tT : tupEnds) {
154 if (!nS.equals(tT)) {
155 if (tc.updateTuple(nS, tT, isInsertion)) {
156 newTuples.updateTuple(nS, tT, true /* deltas implicitly have the same sign as isInsertion*/);
157 notifyTcObservers(nS, tT, isInsertion);
158 }
159 }
160 }
161 }
162 }
163 }
164 }
165 }
166
167 // System.out.println(tc);
168 }
169
170 public ITcRelation<V> getTcRelation() {
171 return this.tc;
172 }
173
174 public void setTcRelation(CountingTcRelation<V> tc) {
175 this.tc = tc;
176 }
177
178 @Override
179 public boolean isReachable(V source, V target) {
180 return tc.containsTuple(source, target);
181 }
182
183 @Override
184 public void attachObserver(ITcObserver<V> to) {
185 this.observers.add(to);
186
187 }
188
189 @Override
190 public void detachObserver(ITcObserver<V> to) {
191 this.observers.remove(to);
192 }
193
194 @Override
195 public Set<V> getAllReachableTargets(V source) {
196 return tc.getTupleEnds(source);
197 }
198
199 @Override
200 public Set<V> getAllReachableSources(V target) {
201 return tc.getTupleStarts(target);
202 }
203
204 private void notifyTcObservers(V source, V target, boolean isInsertion) {
205 if (isInsertion) {
206 for (ITcObserver<V> o : observers) {
207 o.tupleInserted(source, target);
208 }
209 } else {
210 for (ITcObserver<V> o : observers) {
211 o.tupleDeleted(source, target);
212 }
213 }
214 }
215
216 @Override
217 public void dispose() {
218 tc.clear();
219 this.gds.detachObserver(this);
220 }
221
222 @Override
223 public IGraphPathFinder<V> getPathFinder() {
224 return new DFSPathFinder<V>(gds, this);
225 }
226}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingTcRelation.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingTcRelation.java
new file mode 100644
index 00000000..474c7461
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/counting/CountingTcRelation.java
@@ -0,0 +1,259 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.counting;
11
12import java.util.Collections;
13import java.util.List;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.rete.itc.alg.misc.topsort.TopologicalSorting;
17import tools.refinery.viatra.runtime.rete.itc.alg.misc.ITcRelation;
18import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
21import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
22import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
23import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity;
24
25/**
26 * Transitive closure relation implementation for the Counting algorithm.
27 *
28 * @author Tamas Szabo
29 *
30 * @param <V>
31 */
32public class CountingTcRelation<V> implements ITcRelation<V> {
33
34 private IMultiLookup<V, V> tuplesForward = null;
35 private IMultiLookup<V, V> tuplesBackward = null;
36
37 protected CountingTcRelation(boolean backwardIndexing) {
38 tuplesForward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class);
39 if (backwardIndexing)
40 tuplesBackward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class);
41 }
42
43 protected boolean isEmpty() {
44 return 0 == this.tuplesForward.countKeys();
45 }
46
47 protected void clear() {
48 this.tuplesForward.clear();
49
50 if (tuplesBackward != null) {
51 this.tuplesBackward.clear();
52 }
53 }
54
55 protected void union(CountingTcRelation<V> rA) {
56 IMultiLookup<V, V> rForward = rA.tuplesForward;
57 for (V source : rForward.distinctKeys()) {
58 IMemoryView<V> targetBag = rForward.lookup(source);
59 for (V target : targetBag.distinctValues()) {
60 this.addTuple(source, target, targetBag.getCount(target));
61 }
62 }
63 }
64
65 public int getCount(V source, V target) {
66 IMemoryView<V> bucket = tuplesForward.lookup(source);
67 return bucket == null ? 0 : bucket.getCount(target);
68 }
69
70 /**
71 * Returns true if the tc relation did not contain previously such a tuple that is defined by (source,target), false
72 * otherwise (in this case count is incremented with the given count parameter).
73 *
74 * @param source
75 * the source of the tuple
76 * @param target
77 * the target of the tuple
78 * @param count
79 * the count of the tuple, must be positive
80 * @return true if the relation did not contain previously the tuple
81 */
82 public boolean addTuple(V source, V target, int count) {
83 if (tuplesBackward != null) {
84 tuplesBackward.addPairPositiveMultiplicity(target, source, count);
85 }
86
87 ChangeGranularity change =
88 tuplesForward.addPairPositiveMultiplicity(source, target, count);
89
90 return change != ChangeGranularity.DUPLICATE;
91 }
92
93 /**
94 * Derivation count of the tuple (source,target) is incremented or decremented.
95 * Returns true iff updated to / from zero derivation count.
96 * @since 1.7
97 */
98 public boolean updateTuple(V source, V target, boolean isInsertion) {
99 if (isInsertion) {
100 if (tuplesBackward != null) {
101 tuplesBackward.addPair(target, source);
102 }
103 ChangeGranularity change =
104 tuplesForward.addPair(source, target);
105 return change != ChangeGranularity.DUPLICATE;
106 } else {
107 if (tuplesBackward != null) {
108 tuplesBackward.removePair(target, source);
109 }
110 ChangeGranularity change =
111 tuplesForward.removePair(source, target);
112 return change != ChangeGranularity.DUPLICATE;
113 }
114 }
115
116 public void deleteTupleEnd(V deleted) {
117 Set<V> sourcesToDelete = CollectionsFactory.createSet();
118 Set<V> targetsToDelete = CollectionsFactory.createSet();
119
120 for (V target : tuplesForward.lookupOrEmpty(deleted).distinctValues()) {
121 targetsToDelete.add(target);
122 }
123 if (tuplesBackward != null) {
124 for (V source : tuplesBackward.lookupOrEmpty(deleted).distinctValues()) {
125 sourcesToDelete.add(source);
126 }
127 } else {
128 for (V sourceCandidate : tuplesForward.distinctKeys()) {
129 if (tuplesForward.lookupOrEmpty(sourceCandidate).containsNonZero(deleted))
130 sourcesToDelete.add(sourceCandidate);
131 }
132 }
133
134 for (V source : sourcesToDelete) {
135 int count = tuplesForward.lookupOrEmpty(source).getCount(deleted);
136 for (int i=0; i< count; ++i) tuplesForward.removePair(source, deleted);
137 }
138 for (V target : targetsToDelete) {
139 int count = tuplesForward.lookupOrEmpty(deleted).getCount(target);
140 for (int i=0; i< count; ++i) tuplesForward.removePair(deleted, target);
141 }
142
143 if (tuplesBackward != null) {
144 for (V source : sourcesToDelete) {
145 int count = tuplesBackward.lookupOrEmpty(deleted).getCount(source);
146 for (int i=0; i< count; ++i) tuplesBackward.removePair(deleted, source);
147 }
148 for (V target : targetsToDelete) {
149 int count = tuplesBackward.lookupOrEmpty(target).getCount(deleted);
150 for (int i=0; i< count; ++i) tuplesBackward.removePair(target, deleted);
151 }
152 }
153 }
154
155 @Override
156 public String toString() {
157 StringBuilder sb = new StringBuilder("TcRelation = ");
158
159 for (V source : tuplesForward.distinctKeys()) {
160 IMemoryView<V> targets = tuplesForward.lookup(source);
161 for (V target : targets.distinctValues()) {
162 sb.append("{(" + source + "," + target + ")," + targets.getCount(target) + "} ");
163 }
164 }
165
166 return sb.toString();
167 }
168
169 @Override
170 public Set<V> getTupleEnds(V source) {
171 IMemoryView<V> tupEnds = tuplesForward.lookup(source);
172 if (tupEnds == null)
173 return null;
174 return tupEnds.distinctValues();
175 }
176
177 /**
178 * Returns the set of nodes from which the target node is reachable, if already computed.
179 *
180 * @param target
181 * the target node
182 * @return the set of source nodes
183 * @throws UnsupportedOperationException if backwards index not computed
184 */
185 public Set<V> getTupleStarts(V target) {
186 if (tuplesBackward != null) {
187 IMemoryView<V> tupStarts = tuplesBackward.lookup(target);
188 if (tupStarts == null)
189 return null;
190 return tupStarts.distinctValues();
191 } else {
192 throw new UnsupportedOperationException("built without backward indexing");
193 }
194 }
195
196 @Override
197 public Set<V> getTupleStarts() {
198 Set<V> nodes = CollectionsFactory.createSet();
199 for (V s : tuplesForward.distinctKeys()) {
200 nodes.add(s);
201 }
202 return nodes;
203 }
204
205 /**
206 * Returns true if a (source, target) node is present in the transitive closure relation, false otherwise.
207 *
208 * @param source
209 * the source node
210 * @param target
211 * the target node
212 * @return true if tuple is present, false otherwise
213 */
214 public boolean containsTuple(V source, V target) {
215 return tuplesForward.lookupOrEmpty(source).containsNonZero(target);
216 }
217
218 @SuppressWarnings("unchecked")
219 @Override
220 public boolean equals(Object obj) {
221 if (this == obj) {
222 return true;
223 } else if (obj == null || this.getClass() != obj.getClass()) {
224 return false;
225 } else {
226 CountingTcRelation<V> aTR = (CountingTcRelation<V>) obj;
227
228 return tuplesForward.equals(aTR.tuplesForward);
229 }
230 }
231
232 @Override
233 public int hashCode() {
234 return tuplesForward.hashCode();
235 }
236
237 public static <V> CountingTcRelation<V> createFrom(IBiDirectionalGraphDataSource<V> gds) {
238 List<V> topologicalSorting = TopologicalSorting.compute(gds);
239 CountingTcRelation<V> tc = new CountingTcRelation<V>(true);
240 Collections.reverse(topologicalSorting);
241 for (V n : topologicalSorting) {
242 IMemoryView<V> sourceNodes = gds.getSourceNodes(n);
243 Set<V> tupEnds = tc.getTupleEnds(n);
244 for (V s : sourceNodes.distinctValues()) {
245 int count = sourceNodes.getCount(s);
246 for (int i = 0; i < count; i++) {
247 tc.updateTuple(s, n, true);
248 if (tupEnds != null) {
249 for (V t : tupEnds) {
250 tc.updateTuple(s, t, true);
251 }
252 }
253 }
254 }
255 }
256
257 return tc;
258 }
259}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/CountingListener.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/CountingListener.java
new file mode 100644
index 00000000..7d507d82
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/CountingListener.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.alg.incscc;
10
11import tools.refinery.viatra.runtime.rete.itc.igraph.ITcObserver;
12import tools.refinery.viatra.runtime.matchers.util.Direction;
13
14/**
15 * @author Tamas Szabo
16 *
17 */
18public class CountingListener<V> implements ITcObserver<V> {
19
20 private IncSCCAlg<V> alg;
21
22 public CountingListener(IncSCCAlg<V> alg) {
23 this.alg = alg;
24 }
25
26 @Override
27 public void tupleInserted(V source, V target) {
28 alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.INSERT);
29 }
30
31 @Override
32 public void tupleDeleted(V source, V target) {
33 alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.DELETE);
34 }
35
36}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/IncSCCAlg.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/IncSCCAlg.java
new file mode 100644
index 00000000..774e55eb
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/incscc/IncSCCAlg.java
@@ -0,0 +1,609 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.incscc;
11
12import tools.refinery.viatra.runtime.matchers.algorithms.UnionFind;
13import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
14import tools.refinery.viatra.runtime.matchers.util.Direction;
15import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
16import tools.refinery.viatra.runtime.rete.itc.alg.counting.CountingAlg;
17import tools.refinery.viatra.runtime.rete.itc.alg.misc.DFSPathFinder;
18import tools.refinery.viatra.runtime.rete.itc.alg.misc.GraphHelper;
19import tools.refinery.viatra.runtime.rete.itc.alg.misc.IGraphPathFinder;
20import tools.refinery.viatra.runtime.rete.itc.alg.misc.Tuple;
21import tools.refinery.viatra.runtime.rete.itc.alg.misc.bfs.BFS;
22import tools.refinery.viatra.runtime.rete.itc.alg.misc.scc.SCC;
23import tools.refinery.viatra.runtime.rete.itc.alg.misc.scc.SCCResult;
24import tools.refinery.viatra.runtime.rete.itc.alg.util.CollectionHelper;
25import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
26import tools.refinery.viatra.runtime.rete.itc.igraph.*;
27
28import java.util.*;
29import java.util.Map.Entry;
30
31/**
32 * Incremental SCC maintenance + counting algorithm.
33 *
34 * @author Tamas Szabo
35 *
36 * @param <V>
37 * the type parameter of the nodes in the graph data source
38 */
39public class IncSCCAlg<V> implements IGraphObserver<V>, ITcDataSource<V> {
40
41 public UnionFind<V> sccs;
42 public IBiDirectionalGraphDataSource<V> gds;
43 private CountingAlg<V> counting;
44 private Graph<V> reducedGraph;
45 private IBiDirectionalGraphDataSource<V> reducedGraphIndexer;
46 private List<ITcObserver<V>> observers;
47 private CountingListener<V> countingListener;
48
49 public IncSCCAlg(IGraphDataSource<V> graphDataSource) {
50
51 if (graphDataSource instanceof IBiDirectionalGraphDataSource<?>) {
52 gds = (IBiDirectionalGraphDataSource<V>) graphDataSource;
53 } else {
54 gds = new IBiDirectionalWrapper<V>(graphDataSource);
55 }
56 observers = CollectionsFactory.createObserverList();
57 sccs = new UnionFind<V>();
58 reducedGraph = new Graph<V>();
59 reducedGraphIndexer = new IBiDirectionalWrapper<V>(reducedGraph);
60 countingListener = new CountingListener<V>(this);
61 initalizeInternalDataStructures();
62 gds.attachObserver(this);
63 }
64
65 private void initalizeInternalDataStructures() {
66 SCCResult<V> _sccres = SCC.computeSCC(gds);
67 Set<Set<V>> _sccs = _sccres.getSccs();
68
69 for (Set<V> _set : _sccs) {
70 sccs.makeSet(_set);
71 }
72
73 // Initalization of the reduced graph
74 for (V n : sccs.getPartitionHeads()) {
75 reducedGraph.insertNode(n);
76 }
77
78 for (V source : gds.getAllNodes()) {
79 final IMemoryView<V> targetNodes = gds.getTargetNodes(source);
80 for (Entry<V, Integer> entry : targetNodes.entriesWithMultiplicities()) {
81 for (int i = 0; i < entry.getValue(); i++) {
82 V target = entry.getKey();
83 V sourceRoot = sccs.find(source);
84 V targetRoot = sccs.find(target);
85
86 if (!sourceRoot.equals(targetRoot)) {
87 reducedGraph.insertEdge(sourceRoot, targetRoot);
88 }
89 }
90 }
91 }
92
93 counting = new CountingAlg<V>(reducedGraph);
94 }
95
96 @Override
97 public void edgeInserted(V source, V target) {
98 V sourceRoot = sccs.find(source);
99 V targetRoot = sccs.find(target);
100
101 // Different SCC
102 if (!sourceRoot.equals(targetRoot)) {
103
104 // source is reachable from target?
105 if (counting.isReachable(targetRoot, sourceRoot)) {
106
107 Set<V> predecessorRoots = counting.getAllReachableSources(sourceRoot);
108 Set<V> successorRoots = counting.getAllReachableTargets(targetRoot);
109
110 // 1. intersection of source and target roots, these will be in the merged SCC
111 Set<V> isectRoots = CollectionHelper.intersection(predecessorRoots, successorRoots);
112 isectRoots.add(sourceRoot);
113 isectRoots.add(targetRoot);
114
115 // notifications must be issued before Union-Find modifications
116 if (observers.size() > 0) {
117 Set<V> sourceSCCs = createSetNullTolerant(predecessorRoots);
118 sourceSCCs.add(sourceRoot);
119 Set<V> targetSCCs = createSetNullTolerant(successorRoots);
120 targetSCCs.add(targetRoot);
121
122 // tracing back to actual nodes
123 for (V sourceSCC : sourceSCCs) {
124 targetLoop: for (V targetSCC : targetSCCs) {
125 if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop;
126
127 boolean needsNotification =
128 // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc.
129 // Issue notifications only if there is no self-loop present at the moment
130 (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper
131 .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0)
132 ||
133 // Case 2. sourceSCC and targetSCC are different sccs.
134 (!sourceSCC.equals(targetSCC));
135 // if self loop is already present omit the notification
136 if (needsNotification) {
137 notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC),
138 Direction.INSERT);
139 }
140 }
141 }
142 }
143
144 // 2. delete edges, nodes
145 List<V> sourceSCCs = new ArrayList<V>();
146 List<V> targetSCCs = new ArrayList<V>();
147
148 for (V r : isectRoots) {
149 List<V> sourceSCCsOfSCC = getSourceSCCsOfSCC(r);
150 List<V> targetSCCsOfSCC = getTargetSCCsOfSCC(r);
151
152 for (V sourceSCC : sourceSCCsOfSCC) {
153 if (!sourceSCC.equals(r)) {
154 reducedGraph.deleteEdgeIfExists(sourceSCC, r);
155 }
156 }
157
158 for (V targetSCC : targetSCCsOfSCC) {
159 if (!isectRoots.contains(targetSCC) && !r.equals(targetSCC)) {
160 reducedGraph.deleteEdgeIfExists(r, targetSCC);
161 }
162 }
163
164 sourceSCCs.addAll(sourceSCCsOfSCC);
165 targetSCCs.addAll(targetSCCsOfSCC);
166 }
167
168 for (V r : isectRoots) {
169 reducedGraph.deleteNode(r);
170 }
171
172 // 3. union
173 Iterator<V> iterator = isectRoots.iterator();
174 V newRoot = iterator.next();
175 while (iterator.hasNext()) {
176 newRoot = sccs.union(newRoot, iterator.next());
177 }
178
179 // 4. add new node
180 reducedGraph.insertNode(newRoot);
181
182 // 5. add edges
183 Set<V> containedNodes = sccs.getPartition(newRoot);
184
185 for (V sourceSCC : sourceSCCs) {
186 if (!containedNodes.contains(sourceSCC) && !sourceSCC.equals(newRoot)) {
187 reducedGraph.insertEdge(sourceSCC, newRoot);
188 }
189 }
190 for (V targetSCC : targetSCCs) {
191 if (!containedNodes.contains(targetSCC) && !targetSCC.equals(newRoot)) {
192 reducedGraph.insertEdge(newRoot, targetSCC);
193 }
194 }
195 } else {
196 if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 1) {
197 counting.attachObserver(countingListener);
198 }
199 reducedGraph.insertEdge(sourceRoot, targetRoot);
200 counting.detachObserver(countingListener);
201 }
202 } else {
203 // Notifications about self-loops
204 if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1
205 && GraphHelper.getEdgeCount(source, target, gds) == 1) {
206 notifyTcObservers(source, source, Direction.INSERT);
207 }
208 }
209 }
210
211 @Override
212 public void edgeDeleted(V source, V target) {
213 V sourceRoot = sccs.find(source);
214 V targetRoot = sccs.find(target);
215
216 if (!sourceRoot.equals(targetRoot)) {
217 if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 0) {
218 counting.attachObserver(countingListener);
219 }
220 reducedGraph.deleteEdgeIfExists(sourceRoot, targetRoot);
221 counting.detachObserver(countingListener);
222 } else {
223 // get the graph for the scc whose root is sourceRoot
224 Graph<V> g = GraphHelper.getSubGraph(sccs.getPartition(sourceRoot), gds);
225
226 // if source is not reachable from target anymore
227 if (!BFS.isReachable(source, target, g)) {
228 // create copies of the current state before destructive manipulation
229 Map<V, Integer> reachableSources = CollectionsFactory.createMap();
230 for (Entry<V, Integer> entry : reducedGraphIndexer.getSourceNodes(sourceRoot).entriesWithMultiplicities()) {
231 reachableSources.put(entry.getKey(), entry.getValue());
232 }
233 Map<V, Integer> reachableTargets = CollectionsFactory.createMap();
234 for (Entry<V, Integer> entry : reducedGraphIndexer.getTargetNodes(sourceRoot).entriesWithMultiplicities()) {
235 reachableTargets.put(entry.getKey(), entry.getValue());
236 }
237
238 SCCResult<V> _newSccs = SCC.computeSCC(g);
239
240 // delete scc node (and with its edges too)
241 for (Entry<V, Integer> entry : reachableSources.entrySet()) {
242 V s = entry.getKey();
243 for (int i = 0; i < entry.getValue(); i++) {
244 reducedGraph.deleteEdgeIfExists(s, sourceRoot);
245 }
246 }
247
248 for (Entry<V, Integer> entry : reachableTargets.entrySet()) {
249 V t = entry.getKey();
250 for (int i = 0; i < entry.getValue(); i++) {
251 reducedGraph.deleteEdgeIfExists(sourceRoot, t);
252 }
253 }
254
255 sccs.deleteSet(sourceRoot);
256 reducedGraph.deleteNode(sourceRoot);
257
258 Set<Set<V>> newSCCs = _newSccs.getSccs();
259 Set<V> newSCCRoots = CollectionsFactory.createSet();
260
261 // add new nodes and edges to the reduced graph
262 for (Set<V> newSCC : newSCCs) {
263 V newRoot = sccs.makeSet(newSCC);
264 reducedGraph.insertNode(newRoot);
265 newSCCRoots.add(newRoot);
266 }
267 for (V newSCCRoot : newSCCRoots) {
268 List<V> sourceSCCsOfSCC = getSourceSCCsOfSCC(newSCCRoot);
269 List<V> targetSCCsOfSCC = getTargetSCCsOfSCC(newSCCRoot);
270
271 for (V sourceSCC : sourceSCCsOfSCC) {
272 if (!sourceSCC.equals(newSCCRoot)) {
273 reducedGraph.insertEdge(sccs.find(sourceSCC), newSCCRoot);
274 }
275 }
276 for (V targetSCC : targetSCCsOfSCC) {
277 if (!newSCCRoots.contains(targetSCC) && !targetSCC.equals(newSCCRoot))
278 reducedGraph.insertEdge(newSCCRoot, targetSCC);
279 }
280 }
281
282 // Must be after the union-find modifications
283 if (observers.size() > 0) {
284 V newSourceRoot = sccs.find(source);
285 V newTargetRoot = sccs.find(target);
286
287 Set<V> sourceSCCs = createSetNullTolerant(counting.getAllReachableSources(newSourceRoot));
288 sourceSCCs.add(newSourceRoot);
289
290 Set<V> targetSCCs = createSetNullTolerant(counting.getAllReachableTargets(newTargetRoot));
291 targetSCCs.add(newTargetRoot);
292
293 for (V sourceSCC : sourceSCCs) {
294 targetLoop: for (V targetSCC : targetSCCs) {
295 if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop;
296
297 boolean needsNotification =
298 // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc.
299 // Issue notifications only if there is no self-loop present at the moment
300 (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper
301 .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0)
302 ||
303 // Case 2. sourceSCC and targetSCC are different sccs.
304 (!sourceSCC.equals(targetSCC));
305 // if self loop is already present omit the notification
306 if (needsNotification) {
307 notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC),
308 Direction.DELETE);
309 }
310 }
311 }
312 }
313 } else {
314 // only handle self-loop notifications - sourceRoot equals to targetRoot
315 if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1
316 && GraphHelper.getEdgeCount(source, target, gds) == 0) {
317 notifyTcObservers(source, source, Direction.DELETE);
318 }
319 }
320 }
321 }
322
323 @Override
324 public void nodeInserted(V n) {
325 sccs.makeSet(n);
326 reducedGraph.insertNode(n);
327 }
328
329 @Override
330 public void nodeDeleted(V n) {
331 IMemoryView<V> sources = gds.getSourceNodes(n);
332 IMemoryView<V> targets = gds.getTargetNodes(n);
333
334 for (Entry<V, Integer> entry : sources.entriesWithMultiplicities()) {
335 for (int i = 0; i < entry.getValue(); i++) {
336 V source = entry.getKey();
337 edgeDeleted(source, n);
338 }
339 }
340
341 for (Entry<V, Integer> entry : targets.entriesWithMultiplicities()) {
342 for (int i = 0; i < entry.getValue(); i++) {
343 V target = entry.getKey();
344 edgeDeleted(n, target);
345 }
346 }
347
348 sccs.deleteSet(n);
349 }
350
351 @Override
352 public void attachObserver(ITcObserver<V> to) {
353 observers.add(to);
354 }
355
356 @Override
357 public void detachObserver(ITcObserver<V> to) {
358 observers.remove(to);
359 }
360
361 @Override
362 public Set<V> getAllReachableTargets(V source) {
363 V sourceRoot = sccs.find(source);
364 Set<V> containedNodes = sccs.getPartition(sourceRoot);
365 Set<V> targets = CollectionsFactory.createSet();
366
367 if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(source, gds) == 1) {
368 targets.addAll(containedNodes);
369 }
370
371 Set<V> rootSet = counting.getAllReachableTargets(sourceRoot);
372 if (rootSet != null) {
373 for (V _root : rootSet) {
374 targets.addAll(sccs.getPartition(_root));
375 }
376 }
377
378 return targets;
379 }
380
381 @Override
382 public Set<V> getAllReachableSources(V target) {
383 V targetRoot = sccs.find(target);
384 Set<V> containedNodes = sccs.getPartition(targetRoot);
385 Set<V> sources = CollectionsFactory.createSet();
386
387 if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(target, gds) == 1) {
388 sources.addAll(containedNodes);
389 }
390
391 Set<V> rootSet = counting.getAllReachableSources(targetRoot);
392 if (rootSet != null) {
393 for (V _root : rootSet) {
394 sources.addAll(sccs.getPartition(_root));
395 }
396 }
397 return sources;
398 }
399
400 @Override
401 public boolean isReachable(V source, V target) {
402 V sourceRoot = sccs.find(source);
403 V targetRoot = sccs.find(target);
404
405 if (sourceRoot.equals(targetRoot))
406 return true;
407 else
408 return counting.isReachable(sourceRoot, targetRoot);
409 }
410
411 public List<V> getReachabilityPath(V source, V target) {
412 if (!isReachable(source, target)) {
413 return null;
414 } else {
415 Set<V> sccsInSubGraph = CollectionHelper.intersection(counting.getAllReachableTargets(source),
416 counting.getAllReachableSources(target));
417 sccsInSubGraph.add(sccs.find(source));
418 sccsInSubGraph.add(sccs.find(target));
419 Set<V> nodesInSubGraph = CollectionsFactory.createSet();
420
421 for (V sccRoot : sccsInSubGraph) {
422 nodesInSubGraph.addAll(sccs.getPartition(sccRoot));
423 }
424
425 return GraphHelper.constructPath(source, target, nodesInSubGraph, gds);
426 }
427 }
428
429 /**
430 * Return the SCCs from which the SCC represented by the root node is reachable. Note that an SCC can be present
431 * multiple times in the returned list (multiple edges between the two SCCs).
432 *
433 * @param root
434 * @return the list of reachable target SCCs
435 */
436 private List<V> getSourceSCCsOfSCC(V root) {
437 List<V> sourceSCCs = new ArrayList<V>();
438
439 for (V containedNode : this.sccs.getPartition(root)) {
440 IMemoryView<V> sourceNodes = this.gds.getSourceNodes(containedNode);
441 for (V source : sourceNodes.distinctValues()) {
442 sourceSCCs.add(this.sccs.find(source));
443 }
444 }
445
446 return sourceSCCs;
447 }
448
449 /**
450 * Returns true if the SCC represented by the given root node has incoming edges in the reduced graph,
451 * false otherwise (if this SCC is a source in the reduced graph).
452 *
453 * @param root the root node of an SCC
454 * @return true if it has incoming edges, false otherwise
455 * @since 1.6
456 */
457 public boolean hasIncomingEdges(final V root) {
458 for (final V containedNode : this.sccs.getPartition(root)) {
459 final IMemoryView<V> sourceNodes = this.gds.getSourceNodes(containedNode);
460 for (final V source : sourceNodes.distinctValues()) {
461 final V otherRoot = this.sccs.find(source);
462 if (!Objects.equals(root, otherRoot)) {
463 return true;
464 }
465 }
466 }
467 return false;
468 }
469
470 /**
471 * Return the SCCs which are reachable from the SCC represented by the root node. Note that an SCC can be present
472 * multiple times in the returned list (multiple edges between the two SCCs).
473 *
474 * @param root
475 * @return the list of reachable target SCCs
476 */
477 private List<V> getTargetSCCsOfSCC(V root) {
478 List<V> targetSCCs = new ArrayList<V>();
479
480 for (V containedNode : this.sccs.getPartition(root)) {
481 IMemoryView<V> targetNodes = this.gds.getTargetNodes(containedNode);
482 for (V target : targetNodes.distinctValues()) {
483 targetSCCs.add(this.sccs.find(target));
484 }
485 }
486
487 return targetSCCs;
488 }
489
490 /**
491 * Returns true if the SCC represented by the given root node has outgoing edges in the reduced graph,
492 * false otherwise (if this SCC is a sink in the reduced graph).
493 *
494 * @param root the root node of an SCC
495 * @return true if it has outgoing edges, false otherwise
496 * @since 1.6
497 */
498 public boolean hasOutgoingEdges(V root) {
499 for (final V containedNode : this.sccs.getPartition(root)) {
500 final IMemoryView<V> targetNodes = this.gds.getTargetNodes(containedNode);
501 for (final V target : targetNodes.distinctValues()) {
502 final V otherRoot = this.sccs.find(target);
503 if (!Objects.equals(root, otherRoot)) {
504 return true;
505 }
506 }
507 }
508 return false;
509 }
510
511 @Override
512 public void dispose() {
513 gds.detachObserver(this);
514 counting.dispose();
515 }
516
517 /**
518 * Call this method to notify the observers of the transitive closure relation. The tuples used in the notification
519 * will be the Descartes product of the two sets given.
520 *
521 * @param sources
522 * the source nodes
523 * @param targets
524 * the target nodes
525 * @param direction
526 */
527 protected void notifyTcObservers(Set<V> sources, Set<V> targets, Direction direction) {
528 for (V s : sources) {
529 for (V t : targets) {
530 notifyTcObservers(s, t, direction);
531 }
532 }
533 }
534
535 private void notifyTcObservers(V source, V target, Direction direction) {
536 for (ITcObserver<V> observer : observers) {
537 if (direction == Direction.INSERT) {
538 observer.tupleInserted(source, target);
539 }
540 if (direction == Direction.DELETE) {
541 observer.tupleDeleted(source, target);
542 }
543 }
544 }
545
546 /**
547 * Returns the node that is selected as the representative of the SCC containing the argument.
548 * @since 1.6
549 */
550 public V getRepresentative(V node) {
551 return sccs.find(node);
552 }
553
554 public Set<Tuple<V>> getTcRelation() {
555 Set<Tuple<V>> resultSet = new HashSet<Tuple<V>>();
556
557 for (V sourceRoot : sccs.getPartitionHeads()) {
558 Set<V> sources = sccs.getPartition(sourceRoot);
559 if (sources.size() > 1 || GraphHelper.getEdgeCount(sources.iterator().next(), gds) == 1) {
560 for (V source : sources) {
561 for (V target : sources) {
562 resultSet.add(new Tuple<V>(source, target));
563 }
564 }
565 }
566
567 Set<V> reachableTargets = counting.getAllReachableTargets(sourceRoot);
568 if (reachableTargets != null) {
569 for (V targetRoot : reachableTargets) {
570 for (V source : sources) {
571 for (V target : sccs.getPartition(targetRoot)) {
572 resultSet.add(new Tuple<V>(source, target));
573 }
574 }
575 }
576 }
577 }
578
579 return resultSet;
580 }
581
582 public boolean isIsolated(V node) {
583 IMemoryView<V> targets = gds.getTargetNodes(node);
584 IMemoryView<V> sources = gds.getSourceNodes(node);
585 return targets.isEmpty() && sources.isEmpty();
586 }
587
588 @Override
589 public IGraphPathFinder<V> getPathFinder() {
590 return new DFSPathFinder<V>(gds, this);
591 }
592
593 /**
594 * The graph of SCCs; each SCC is represented by its representative node (see {@link #getRepresentative(Object)})
595 * @since 1.6
596 */
597 public Graph<V> getReducedGraph() {
598 return reducedGraph;
599 }
600
601 private static <V> Set<V> createSetNullTolerant(Set<V> initial) {
602 if (initial != null)
603 return CollectionsFactory.createSet(initial);
604 else
605 return CollectionsFactory.createSet();
606 }
607
608
609}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/DFSPathFinder.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/DFSPathFinder.java
new file mode 100644
index 00000000..2cec33a2
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/DFSPathFinder.java
@@ -0,0 +1,146 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Abel Hegedus and IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.alg.misc;
10
11import java.util.ArrayList;
12import java.util.Deque;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
20import tools.refinery.viatra.runtime.rete.itc.igraph.ITcDataSource;
21import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
22
23/**
24 * A depth-first search implementation of the {@link IGraphPathFinder}.
25 *
26 * TODO use ITC to filter nodes that must be traversed, instead of checks
27 *
28 * @author Abel Hegedus
29 *
30 * @param <V>
31 * the node type of the graph
32 */
33public class DFSPathFinder<V> implements IGraphPathFinder<V> {
34
35 private IGraphDataSource<V> graph;
36 private ITcDataSource<V> itc;
37
38 public DFSPathFinder(IGraphDataSource<V> graph, ITcDataSource<V> itc) {
39 this.graph = graph;
40 this.itc = itc;
41 }
42
43 @Override
44 public Iterable<Deque<V>> getAllPaths(V sourceNode, V targetNode) {
45 Set<V> endNodes = new HashSet<V>();
46 endNodes.add(targetNode);
47 return getAllPathsToTargets(sourceNode, endNodes);
48 }
49
50 @Override
51 public Iterable<Deque<V>> getAllPathsToTargets(V sourceNode, Set<V> targetNodes) {
52 List<Deque<V>> paths = new ArrayList<Deque<V>>();
53 Deque<V> visited = new LinkedList<V>();
54 Set<V> reachableTargets = new HashSet<V>();
55 for (V targetNode : targetNodes) {
56 if (itc.isReachable(sourceNode, targetNode)) {
57 reachableTargets.add(targetNode);
58 }
59 }
60 if (!reachableTargets.isEmpty()) {
61 return paths;
62 }
63 visited.add(sourceNode);
64 return getPaths(paths, visited, reachableTargets);
65 }
66
67 protected Iterable<Deque<V>> getPaths(List<Deque<V>> paths, Deque<V> visited, Set<V> targetNodes) {
68 IMemoryView<V> nodes = graph.getTargetNodes(visited.getLast());
69 // examine adjacent nodes
70 for (V node : nodes.distinctValues()) {
71 if (visited.contains(node)) {
72 continue;
73 }
74 if (targetNodes.contains(node)) {
75 visited.add(node);
76 // clone visited LinkedList
77 Deque<V> visitedClone = new LinkedList<V>(visited);
78 paths.add(visitedClone);
79 visited.removeLast();
80 break;
81 }
82 }
83
84 // in breadth-first, recursion needs to come after visiting connected nodes
85 for (V node : nodes.distinctValues()) {
86 if (visited.contains(node) || targetNodes.contains(node)) {
87 continue;
88 }
89 boolean canReachTarget = false;
90 for (V target : targetNodes) {
91 if (itc.isReachable(node, target)) {
92 canReachTarget = true;
93 break;
94 }
95 }
96 if (canReachTarget) {
97 visited.addLast(node);
98 getPaths(paths, visited, targetNodes);
99 visited.removeLast();
100 }
101 }
102
103 return paths;
104 }
105
106 public String printPaths(List<Deque<V>> paths) {
107 StringBuilder sb = new StringBuilder();
108 for (Deque<V> visited : paths) {
109 sb.append("Path: ");
110 for (V node : visited) {
111 sb.append(node);
112 sb.append(" --> ");
113 }
114 sb.append("\n");
115 }
116 return sb.toString();
117 }
118
119 @Override
120 public Deque<V> getPath(V sourceNode, V targetNode) {
121 // TODO optimize
122 Iterable<Deque<V>> allPaths = getAllPaths(sourceNode, targetNode);
123 Iterator<Deque<V>> pathIterator = allPaths.iterator();
124 return pathIterator.hasNext() ? pathIterator.next() : new LinkedList<V>();
125 }
126
127 @Override
128 public Iterable<Deque<V>> getShortestPaths(V sourceNode, V targetNode) {
129 // TODO optimize
130 Iterable<Deque<V>> allPaths = getAllPaths(sourceNode, targetNode);
131 List<Deque<V>> shortestPaths = new ArrayList<Deque<V>>();
132 int shortestPathLength = -1;
133 for (Deque<V> path : allPaths) {
134 int pathLength = path.size();
135 if (shortestPathLength == -1 || pathLength < shortestPathLength) {
136 shortestPaths.clear();
137 shortestPathLength = pathLength;
138 }
139 if (pathLength == shortestPathLength) {
140 shortestPaths.add(path);
141 }
142 }
143 return shortestPaths;
144 }
145
146}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Edge.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Edge.java
new file mode 100644
index 00000000..862c99b3
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Edge.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc;
11
12public class Edge<V> {
13 private V source;
14 private V target;
15
16 public Edge(V source, V target) {
17 super();
18 this.source = source;
19 this.target = target;
20 }
21
22 public V getSource() {
23 return source;
24 }
25
26 public void setSource(V source) {
27 this.source = source;
28 }
29
30 public V getTarget() {
31 return target;
32 }
33
34 public void setTarget(V target) {
35 this.target = target;
36 }
37
38}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/GraphHelper.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/GraphHelper.java
new file mode 100644
index 00000000..b79e4d45
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/GraphHelper.java
@@ -0,0 +1,169 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.alg.misc;
10
11import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
12import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
13import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
14import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
15
16import java.util.*;
17import java.util.Map.Entry;
18
19/**
20 * Utility class for graph related operations.
21 *
22 * @author Tamas Szabo
23 */
24public class GraphHelper {
25
26 private GraphHelper() {/*Utility class constructor*/}
27
28 /**
29 * Returns the subgraph from the given {@link IBiDirectionalGraphDataSource} which contains the given set of nodes.
30 *
31 * @param nodesInSubGraph
32 * the nodes that are present in the subgraph
33 * @param graphDataSource
34 * the graph data source for the original graph
35 * @return the subgraph associated to the given nodes
36 */
37 public static <V> Graph<V> getSubGraph(Collection<V> nodesInSubGraph,
38 IBiDirectionalGraphDataSource<V> graphDataSource) {
39 Graph<V> g = new Graph<V>();
40 if (nodesInSubGraph != null) {
41 for (V node : nodesInSubGraph) {
42 g.insertNode(node);
43 }
44
45 for (V node : nodesInSubGraph) {
46 IMemoryView<V> sources = graphDataSource.getSourceNodes(node);
47 for (Entry<V, Integer> entry : sources.entriesWithMultiplicities()) {
48 for (int i = 0; i < entry.getValue(); i++) {
49 V s = entry.getKey();
50 if (nodesInSubGraph.contains(s)) {
51 g.insertEdge(s, node);
52 }
53 }
54 }
55 }
56 }
57
58 return g;
59 }
60
61 /**
62 * Constructs a path between source and target in the given graph. Both the {@link IGraphDataSource} and the set of
63 * nodes are used, this way it is possible to construct a path in a given subgraph.
64 *
65 * The returned {@link List} contains the nodes along the path (this means that there is an edge in the graph
66 * between two consecutive nodes). A self loop (one edge) is indicated with the source node being present two times
67 * in the returned {@link List}.
68 *
69 * @param source
70 * the source node
71 * @param target
72 * the target node
73 * @param nodesInGraph
74 * the nodes that are present in the subgraph
75 * @param graphDataSource
76 * the graph data source
77 * @return the path between the two nodes
78 */
79 public static <V> List<V> constructPath(V source, V target, Set<V> nodesInGraph,
80 IGraphDataSource<V> graphDataSource) {
81 Set<V> visitedNodes = new HashSet<V>();
82 List<V> path = new ArrayList<V>();
83
84 visitedNodes.add(source);
85 path.add(source);
86 V act = source;
87
88 // if source and target are the same node
89 if (source.equals(target) && graphDataSource.getTargetNodes(source).containsNonZero(target)) {
90 // the node will be present in the path two times
91 path.add(source);
92 return path;
93 } else {
94 while (act != null) {
95 V nextNode = getNextNodeToVisit(act, graphDataSource, nodesInGraph, visitedNodes);
96 if (nextNode == null && path.size() > 1) {
97 // needs to backtrack along path
98 // remove the last element in the path because we can't go
99 // anywhere from there
100 path.remove(path.size() - 1);
101 while (nextNode == null && path.size() > 0) {
102 V lastPathElement = path.get(path.size() - 1);
103 nextNode = getNextNodeToVisit(lastPathElement, graphDataSource, nodesInGraph, visitedNodes);
104 if (nextNode == null) {
105 path.remove(path.size() - 1);
106 }
107 }
108 }
109
110 if (nextNode != null) {
111 visitedNodes.add(nextNode);
112 path.add(nextNode);
113 if (nextNode.equals(target)) {
114 return path;
115 }
116 }
117 act = nextNode;
118 }
119 return null;
120 }
121 }
122
123 private static <V> V getNextNodeToVisit(V act, IGraphDataSource<V> graphDataSource, Set<V> nodesInSubGraph,
124 Set<V> visitedNodes) {
125 IMemoryView<V> targetNodes = graphDataSource.getTargetNodes(act);
126 for (Entry<V, Integer> entry : targetNodes.entriesWithMultiplicities()) {
127 for (int i = 0; i < entry.getValue(); i++) {
128 V node = entry.getKey();
129 if (nodesInSubGraph.contains(node) && !visitedNodes.contains(node)) {
130 return node;
131 }
132 }
133 }
134 return null;
135 }
136
137 /**
138 * Returns the number of self-loop edges for the given node.
139 *
140 * @param node
141 * the node
142 * @param graphDataSource
143 * the graph data source
144 * @return the number of self-loop edges
145 */
146 public static <V> int getEdgeCount(V node, IGraphDataSource<V> graphDataSource) {
147 return getEdgeCount(node, node, graphDataSource);
148 }
149
150 /**
151 * Returns the number of edges between the given source and target nodes.
152 *
153 * @param source
154 * the source node
155 * @param target
156 * the target node
157 * @param graphDataSource
158 * the graph data source
159 * @return the number of parallel edges between the two nodes
160 */
161 public static <V> int getEdgeCount(V source, V target, IGraphDataSource<V> graphDataSource) {
162 Integer count = graphDataSource.getTargetNodes(source).getCount(target);
163 if (count == null) {
164 return 0;
165 } else {
166 return count;
167 }
168 }
169}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/IGraphPathFinder.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/IGraphPathFinder.java
new file mode 100644
index 00000000..624f9f7d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/IGraphPathFinder.java
@@ -0,0 +1,67 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.alg.misc;
10
11import java.util.Deque;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.rete.itc.igraph.ITcDataSource;
15
16/**
17 * The path finder provides methods for retrieving paths in a graph between a source node and one or more target nodes.
18 * Use {@link ITcDataSource#getPathFinder()} for instantiating.
19 *
20 * @author Abel Hegedus
21 *
22 * @param <V> the node type of the graph
23 */
24public interface IGraphPathFinder<V> {
25
26 /**
27 * Returns an arbitrary path from the source node to the target node (if such exists). If there is no path
28 * between them, an empty collection is returned.
29 *
30 * @param sourceNode the source node of the path
31 * @param targetNode the target node of the path
32 * @return the path from the source to the target, or empty collection if target is not reachable from source.
33 */
34 Deque<V> getPath(V sourceNode, V targetNode);
35
36 /**
37 * Returns the collection of shortest paths from the source node to the target node (if such exists). If there is no path
38 * between them, an empty collection is returned.
39 *
40 * @param sourceNode the source node of the path
41 * @param targetNode the target node of the path
42 * @return the collection of shortest paths from the source to the target, or empty collection if target is not reachable from source.
43 */
44 Iterable<Deque<V>> getShortestPaths(V sourceNode, V targetNode);
45
46 /**
47 * Returns the collection of paths from the source node to the target node (if such exists). If there is no path
48 * between them, an empty collection is returned.
49 *
50 * @param sourceNode the source node of the path
51 * @param targetNode the target node of the path
52 * @return the collection of paths from the source to the target, or empty collection if target is not reachable from source.
53 */
54 Iterable<Deque<V>> getAllPaths(V sourceNode, V targetNode);
55
56 /**
57 * Returns the collection of paths from the source node to any of the target nodes (if such exists). If there is no path
58 * between them, an empty collection is returned.
59 *
60 * @param sourceNode the source node of the path
61 * @param targetNodes the set of target nodes of the paths
62 * @return the collection of paths from the source to any of the targets, or empty collection if neither target is reachable from source.
63 */
64 Iterable<Deque<V>> getAllPathsToTargets(V sourceNode, Set<V> targetNodes);
65
66
67}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/ITcRelation.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/ITcRelation.java
new file mode 100644
index 00000000..9fd85ae1
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/ITcRelation.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc;
11
12import java.util.Set;
13
14public interface ITcRelation<V> {
15
16 /**
17 * Returns the starting nodes from a transitive closure relation.
18 *
19 * @return the set of starting nodes
20 */
21 public Set<V> getTupleStarts();
22
23 /**
24 * Returns the set of nodes that are reachable from the given node.
25 *
26 * @param start
27 * the starting node
28 * @return the set of reachable nodes
29 */
30 public Set<V> getTupleEnds(V start);
31}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Tuple.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Tuple.java
new file mode 100644
index 00000000..84c79dcf
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/Tuple.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc;
11
12public class Tuple<V> {
13
14 private V source;
15 private V target;
16
17 public Tuple(V source, V target) {
18 super();
19 this.source = source;
20 this.target = target;
21 }
22
23 public V getSource() {
24 return source;
25 }
26
27 public void setSource(V source) {
28 this.source = source;
29 }
30
31 public V getTarget() {
32 return target;
33 }
34
35 public void setTarget(V target) {
36 this.target = target;
37 }
38
39 @Override
40 public String toString() {
41 return "(" + source.toString() + "," + target.toString() + ")";
42 }
43
44 @Override
45 public boolean equals(Object o) {
46 if (o instanceof Tuple) {
47 Tuple<?> t = (Tuple<?>) o;
48
49 if (t.getSource().equals(this.source) && t.getTarget().equals(this.target)) {
50 return true;
51 }
52 }
53 return false;
54 }
55
56 @Override
57 public int hashCode() {
58 return source.hashCode() + target.hashCode();
59 }
60}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/bfs/BFS.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/bfs/BFS.java
new file mode 100644
index 00000000..22ce8962
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/bfs/BFS.java
@@ -0,0 +1,148 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.bfs;
11
12import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
13import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
14
15import java.util.*;
16
17public class BFS<V> {
18
19 private BFS() {/*Utility class constructor*/}
20
21 /**
22 * Performs a breadth first search on the given graph to determine whether source is reachable from target.
23 *
24 * @param <V>
25 * the type parameter of the nodes in the graph
26 * @param source
27 * the source node
28 * @param target
29 * the target node
30 * @param graph
31 * the graph data source
32 * @return true if source is reachable from target, false otherwise
33 */
34 public static <V> boolean isReachable(V source, V target, IGraphDataSource<V> graph) {
35 Deque<V> nodeQueue = new ArrayDeque<V>();
36 Set<V> visited = new HashSet<V>();
37
38 nodeQueue.add(source);
39 visited.add(source);
40
41 boolean ret = _isReachable(target, graph, nodeQueue, visited);
42 return ret;
43 }
44
45 private static <V> boolean _isReachable(V target, IGraphDataSource<V> graph, Deque<V> nodeQueue, Set<V> visited) {
46
47 while (!nodeQueue.isEmpty()) {
48 V node = nodeQueue.removeFirst();
49 for (V t : graph.getTargetNodes(node).distinctValues()){
50 if (t.equals(target)) {
51 return true;
52 }
53 if (!visited.contains(t)) {
54 visited.add(t);
55 nodeQueue.addLast(t);
56 }
57 }
58 }
59 return false;
60 }
61
62 public static <V> Set<V> reachableSources(IBiDirectionalGraphDataSource<V> graph, V target) {
63 Set<V> retSet = new HashSet<V>();
64 retSet.add(target);
65 Deque<V> nodeQueue = new ArrayDeque<V>();
66 nodeQueue.add(target);
67
68 _reachableSources(graph, nodeQueue, retSet);
69
70 return retSet;
71 }
72
73 private static <V> void _reachableSources(IBiDirectionalGraphDataSource<V> graph, Deque<V> nodeQueue,
74 Set<V> retSet) {
75 while (!nodeQueue.isEmpty()) {
76 V node = nodeQueue.removeFirst();
77 for (V _node : graph.getSourceNodes(node).distinctValues()) {
78 if (!retSet.contains(_node)) {
79 retSet.add(_node);
80 nodeQueue.addLast(_node);
81 }
82 }
83 }
84 }
85
86 public static <V> Set<V> reachableTargets(IGraphDataSource<V> graph, V source) {
87 Set<V> retSet = new HashSet<V>();
88 retSet.add(source);
89 Deque<V> nodeQueue = new ArrayDeque<V>();
90 nodeQueue.add(source);
91
92 _reachableTargets(graph, nodeQueue, retSet);
93
94 return retSet;
95 }
96
97 private static <V> void _reachableTargets(IGraphDataSource<V> graph, Deque<V> nodeQueue, Set<V> retSet) {
98 while (!nodeQueue.isEmpty()) {
99 V node = nodeQueue.removeFirst();
100
101 for (V _node : graph.getTargetNodes(node).distinctValues()) {
102
103 if (!retSet.contains(_node)) {
104 retSet.add(_node);
105 nodeQueue.addLast(_node);
106 }
107 }
108 }
109 }
110
111 /**
112 * Performs a breadth first search on the given graph and collects all the nodes along the path from source to
113 * target if such path exists.
114 *
115 * @param <V>
116 * the type parameter of the nodes in the graph
117 * @param source
118 * the source node
119 * @param target
120 * the target node
121 * @param graph
122 * the graph data source
123 * @return the set of nodes along the path
124 */
125 public static <V> Set<V> collectNodesAlongPath(V source, V target, IGraphDataSource<V> graph) {
126 Set<V> path = new HashSet<V>();
127 _collectNodesAlongPath(source, target, graph, path);
128 return path;
129 }
130
131 private static <V> boolean _collectNodesAlongPath(V node, V target, IGraphDataSource<V> graph, Set<V> path) {
132
133 boolean res = false;
134
135 // end recursion
136 if (node.equals(target)) {
137 path.add(node);
138 return true;
139 } else {
140 for (V _nodeT : graph.getTargetNodes(node).distinctValues()) {
141 res = (_collectNodesAlongPath(_nodeT, target, graph, path)) || res;
142 }
143 if (res)
144 path.add(node);
145 return res;
146 }
147 }
148}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/PKAlg.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/PKAlg.java
new file mode 100644
index 00000000..892d048e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/PKAlg.java
@@ -0,0 +1,179 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.scc;
11
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.List;
16import java.util.Map;
17
18import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
19import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalWrapper;
20import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
21import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphObserver;
22
23public class PKAlg<V> implements IGraphObserver<V> {
24
25 /**
26 * Maps the nodes to their indicies.
27 */
28 private Map<V, Integer> node2index;
29 private Map<Integer, V> index2node;
30 private Map<V, Boolean> node2mark;
31
32 /**
33 * Maps the index of a node to the index in the topsort.
34 */
35 private Map<Integer, Integer> index2topsort;
36 private Map<Integer, Integer> topsort2index;
37
38 /**
39 * Index associated to the inserted nodes (incrementing with every insertion).
40 */
41 private int index;
42
43 /**
44 * Index within the topsort for the target node when edge insertion occurs.
45 */
46 private int lower_bound;
47
48 /**
49 * Index within the topsort for the source node when edge insertion occurs.
50 */
51 private int upper_bound;
52
53 private List<V> RF;
54 private List<V> RB;
55 private IBiDirectionalGraphDataSource<V> gds;
56
57 public PKAlg(IGraphDataSource<V> gds) {
58 if (gds instanceof IBiDirectionalGraphDataSource<?>) {
59 this.gds = (IBiDirectionalGraphDataSource<V>) gds;
60 } else {
61 this.gds = new IBiDirectionalWrapper<V>(gds);
62 }
63
64 node2mark = new HashMap<V, Boolean>();
65 node2index = new HashMap<V, Integer>();
66 index2node = new HashMap<Integer, V>();
67 index2topsort = new HashMap<Integer, Integer>();
68 topsort2index = new HashMap<Integer, Integer>();
69 index = 0;
70
71 gds.attachObserver(this);
72 }
73
74 @Override
75 public void edgeInserted(V source, V target) {
76
77 RF = new ArrayList<V>();
78 RB = new ArrayList<V>();
79
80 lower_bound = index2topsort.get(node2index.get(target));
81 upper_bound = index2topsort.get(node2index.get(source));
82
83 if (lower_bound < upper_bound) {
84 dfsForward(target);
85 dfsBackward(source);
86 reorder();
87 }
88 }
89
90 private List<Integer> getIndicies(List<V> list) {
91 List<Integer> indicies = new ArrayList<Integer>();
92
93 for (V n : list)
94 indicies.add(index2topsort.get(node2index.get(n)));
95
96 return indicies;
97 }
98
99 private void reorder() {
100
101 Collections.reverse(RB);
102
103 // azon csomopontok indexei amelyek sorrendje nem jo
104 List<Integer> L = getIndicies(RF);
105 L.addAll(getIndicies(RB));
106 Collections.sort(L);
107
108 for (int i = 0; i < RB.size(); i++) {
109 index2topsort.put(node2index.get(RB.get(i)), L.get(i));
110 topsort2index.put(L.get(i), node2index.get(RB.get(i)));
111 }
112
113 for (int i = 0; i < RF.size(); i++) {
114 index2topsort.put(node2index.get(RF.get(i)), L.get(i + RB.size()));
115 topsort2index.put(L.get(i + RB.size()), node2index.get(RF.get(i)));
116 }
117 }
118
119 @SuppressWarnings("unused")
120 private List<V> getTopSort() {
121 List<V> topsort = new ArrayList<V>();
122
123 for (int i : topsort2index.values()) {
124 topsort.add(index2node.get(i));
125 }
126
127 return topsort;
128 }
129
130 private void dfsBackward(V node) {
131 node2mark.put(node, true);
132 RB.add(node);
133
134 for (V sn : gds.getSourceNodes(node).distinctValues()) {
135 int top_id = index2topsort.get(node2index.get(sn));
136
137 if (!node2mark.get(sn) && lower_bound < top_id)
138 dfsBackward(sn);
139 }
140 }
141
142 private void dfsForward(V node) {
143 node2mark.put(node, true);
144 RF.add(node);
145
146 for (V tn : gds.getTargetNodes(node).distinctValues()) {
147 int top_id = index2topsort.get(node2index.get(tn));
148
149 if (top_id == upper_bound)
150 System.out.println("!!!Cycle detected!!!");
151 else if (!node2mark.get(tn) && top_id < upper_bound)
152 dfsForward(tn);
153 }
154 }
155
156 @Override
157 public void edgeDeleted(V source, V target) {
158 // Edge deletion does not affect topsort
159 }
160
161 @Override
162 public void nodeInserted(V n) {
163 node2mark.put(n, false);
164 node2index.put(n, index);
165 index2node.put(index, n);
166 index2topsort.put(index, index);
167 topsort2index.put(index, index);
168 index++;
169 }
170
171 @Override
172 public void nodeDeleted(V n) {
173 node2mark.remove(n);
174 int node_id = node2index.remove(n);
175 index2node.remove(node_id);
176 int top_id = index2topsort.remove(node_id);
177 topsort2index.remove(top_id);
178 }
179}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCC.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCC.java
new file mode 100644
index 00000000..de070839
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCC.java
@@ -0,0 +1,143 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.scc;
11
12import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
13import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
14
15import java.util.*;
16
17/**
18 * Efficient algorithms to compute the Strongly Connected Components in a directed graph.
19 *
20 * @author Tamas Szabo
21 *
22 * @param <V>
23 * the type parameter of the nodes in the graph
24 */
25public class SCC<V> {
26
27 private SCC() {/*Utility class constructor*/}
28
29 public static long sccId = 0;
30
31 /**
32 * Computes the SCCs for the given graph and returns them as a multiset. (Iterative version of Tarjan's algorithm)
33 *
34 * @param g
35 * the directed graph data source
36 * @return the set of SCCs
37 */
38 public static <V> SCCResult<V> computeSCC(IGraphDataSource<V> g) {
39 int index = 0;
40 Set<Set<V>> ret = new HashSet<Set<V>>();
41
42 // stores the lowlink and index information for the given node
43 Map<V, SCCProperty> nodeMap = CollectionsFactory.createMap();
44
45 // stores all target nodes of a given node - the list will be modified
46 Map<V, Set<V>> targetNodeMap = CollectionsFactory.createMap();
47
48 // stores those target nodes for a given node which have not been visited
49 Map<V, Set<V>> notVisitedMap = CollectionsFactory.createMap();
50
51 // stores the nodes during the traversal
52 Deque<V> nodeStack = new ArrayDeque<V>();
53
54 // stores the nodes which belong to an scc (there can be many sccs in the stack at the same time)
55 Deque<V> sccStack = new ArrayDeque<V>();
56
57 boolean sink = false, finishedTraversal = true;
58
59 // initialize all nodes with 0 index and 0 lowlink
60 Set<V> allNodes = g.getAllNodes();
61 for (V n : allNodes) {
62 nodeMap.put(n, new SCCProperty(0, 0));
63 }
64
65 for (V n : allNodes) {
66 // if the node has not been visited yet
67 if (nodeMap.get(n).getIndex() == 0) {
68 nodeStack.push(n);
69
70 while (!nodeStack.isEmpty()) {
71 V currentNode = nodeStack.peekLast();
72 sink = false;
73 finishedTraversal = false;
74 SCCProperty prop = nodeMap.get(currentNode);
75
76 if (nodeMap.get(currentNode).getIndex() == 0) {
77 index++;
78 sccStack.addLast(currentNode);
79 prop.setIndex(index);
80 prop.setLowlink(index);
81
82 notVisitedMap.put(currentNode, new HashSet<V>());
83
84 // storing the target nodes of the actual node
85 if (g.getTargetNodes(currentNode) != null) {
86 Set<V> targets = g.getTargetNodes(currentNode).distinctValues();
87 targetNodeMap.put(currentNode, CollectionsFactory.createSet(targets));
88 }
89 }
90
91 if (targetNodeMap.get(currentNode) != null) {
92
93 // remove node from stack, the exploration of its children has finished
94 if (targetNodeMap.get(currentNode).size() == 0) {
95 targetNodeMap.remove(currentNode);
96
97 nodeStack.removeLast();
98
99 for (V targetNode : g.getTargetNodes(currentNode).distinctValues()) {
100 if (notVisitedMap.get(currentNode).contains(targetNode)) {
101 prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getLowlink()));
102 } else if (sccStack.contains(targetNode)) {
103 prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getIndex()));
104 }
105 }
106
107 finishedTraversal = true;
108 } else {
109 V targetNode = targetNodeMap.get(currentNode).iterator().next();
110 targetNodeMap.get(currentNode).remove(targetNode);
111 // if the targetNode has not yet been visited push it to the stack
112 // and mark it in the notVisitedMap
113 if (nodeMap.get(targetNode).getIndex() == 0) {
114 notVisitedMap.get(currentNode).add(targetNode);
115 nodeStack.addLast(targetNode);
116 }
117 }
118 }
119 // if currentNode has no target nodes
120 else {
121 nodeStack.removeLast();
122 sink = true;
123 }
124
125 // create scc if node is a sink or an scc has been found
126 if ((sink || finishedTraversal) && (prop.getLowlink() == prop.getIndex())) {
127 Set<V> sc = new HashSet<V>();
128 V targetNode = null;
129
130 do {
131 targetNode = sccStack.removeLast();
132 sc.add(targetNode);
133 } while (!targetNode.equals(currentNode));
134
135 ret.add(sc);
136 }
137 }
138 }
139 }
140
141 return new SCCResult<V>(ret, g);
142 }
143}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCProperty.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCProperty.java
new file mode 100644
index 00000000..51ee834e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCProperty.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.scc;
11
12public class SCCProperty {
13 private int index;
14 private int lowlink;
15
16 public SCCProperty(int index, int lowlink) {
17 super();
18 this.index = index;
19 this.lowlink = lowlink;
20 }
21
22 public int getIndex() {
23 return index;
24 }
25
26 public void setIndex(int index) {
27 this.index = index;
28 }
29
30 public int getLowlink() {
31 return lowlink;
32 }
33
34 public void setLowlink(int lowlink) {
35 this.lowlink = lowlink;
36 }
37}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCResult.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCResult.java
new file mode 100644
index 00000000..2e511fd6
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/scc/SCCResult.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.scc;
11
12import java.util.Map.Entry;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
16
17public class SCCResult<V> {
18
19 private Set<Set<V>> sccs;
20 private IGraphDataSource<V> gds;
21
22 public SCCResult(Set<Set<V>> sccs, IGraphDataSource<V> gds) {
23 this.sccs = sccs;
24 this.gds = gds;
25 }
26
27 public Set<Set<V>> getSccs() {
28 return sccs;
29 }
30
31 public int getSCCCount() {
32 return sccs.size();
33 }
34
35 public double getAverageNodeCount() {
36 double a = 0;
37
38 for (Set<V> s : sccs) {
39 a += s.size();
40 }
41
42 return a / sccs.size();
43 }
44
45 public double getAverageEdgeCount() {
46 long edgeSum = 0;
47
48 for (Set<V> scc : sccs) {
49 for (V source : scc) {
50 for (Entry<V, Integer> entry : gds.getTargetNodes(source).entriesWithMultiplicities()) {
51 if (scc.contains(entry.getKey())) {
52 edgeSum += entry.getValue();
53 }
54 }
55 }
56 }
57
58 return (double) edgeSum / (double) sccs.size();
59 }
60
61 public int getBiggestSCCSize() {
62 int max = 0;
63
64 for (Set<V> scc : sccs) {
65 if (scc.size() > max)
66 max = scc.size();
67 }
68
69 return max;
70 }
71
72 public long getSumOfSquares() {
73 long sum = 0;
74
75 for (Set<V> scc : sccs) {
76 sum += scc.size() * scc.size();
77 }
78
79 return sum;
80 }
81}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/topsort/TopologicalSorting.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/topsort/TopologicalSorting.java
new file mode 100644
index 00000000..89be6804
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/misc/topsort/TopologicalSorting.java
@@ -0,0 +1,73 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.alg.misc.topsort;
11
12import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
13
14import java.util.*;
15
16/**
17 * @since 1.6
18 */
19public class TopologicalSorting {
20
21 private TopologicalSorting() {/*Utility class constructor*/}
22
23 private static final class Pair<T> {
24 public T element;
25 public boolean isParent;
26
27 public Pair(final T element, final boolean isParent) {
28 this.element = element;
29 this.isParent = isParent;
30 }
31 }
32
33 /**
34 * Returns a topological ordering for the given graph data source.
35 * Output format: if there is an a -> b (transitive) reachability, then node <code>a</code> will come before node <code>b</code> in the resulting list.
36 *
37 * @param gds the graph data source
38 * @return a topological ordering
39 */
40 public static <T> List<T> compute(final IGraphDataSource<T> gds) {
41 final Set<T> visited = new HashSet<T>();
42 final LinkedList<T> result = new LinkedList<T>();
43 final Deque<Pair<T>> dfsStack = new ArrayDeque<Pair<T>>();
44
45 for (final T node : gds.getAllNodes()) {
46 if (!visited.contains(node)) {
47 dfsStack.addLast(new Pair<T>(node, false));
48 }
49
50 while (!dfsStack.isEmpty()) {
51 final Pair<T> head = dfsStack.removeLast();
52 final T source = head.element;
53
54 if (head.isParent) {
55 // we have already seen source, push it to the resulting stack
56 result.addFirst(source);
57 } else {
58 // first time we see source, continue with its children
59 visited.add(source);
60 dfsStack.addLast(new Pair<T>(source, true));
61
62 for (final T target : gds.getTargetNodes(source).distinctValues()) {
63 if (!visited.contains(target)) {
64 dfsStack.addLast(new Pair<T>(target, false));
65 }
66 }
67 }
68 }
69 }
70
71 return result;
72 }
73}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java
new file mode 100644
index 00000000..794dabc0
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java
@@ -0,0 +1,174 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.rete.itc.alg.representative;
7
8import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
9import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphObserver;
10import tools.refinery.viatra.runtime.matchers.util.Direction;
11
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Map;
15import java.util.Set;
16
17public abstract class RepresentativeElectionAlgorithm implements IGraphObserver<Object> {
18 protected final Graph<Object> graph;
19 protected final Map<Object, Object> representatives = new HashMap<>();
20 protected final Map<Object, Set<Object>> components = new HashMap<>();
21 private RepresentativeObserver observer;
22
23 protected RepresentativeElectionAlgorithm(Graph<Object> graph) {
24 this.graph = graph;
25 initializeComponents();
26 graph.attachObserver(this);
27 }
28
29 protected abstract void initializeComponents();
30
31 protected void initializeSet(Set<Object> set) {
32 var iterator = set.iterator();
33 if (!iterator.hasNext()) {
34 // Set is empty.
35 return;
36 }
37 var representative = iterator.next();
38 for (var node : set) {
39 var oldRepresentative = representatives.put(node, representative);
40 if (oldRepresentative != null && !representative.equals(oldRepresentative)) {
41 throw new IllegalStateException("Node %s is already in a set represented by %s, cannot add it to %s"
42 .formatted(node, oldRepresentative, set));
43 }
44 }
45 components.put(representative, set);
46 }
47
48 protected void merge(Set<Object> toMerge) {
49 if (toMerge.isEmpty()) {
50 return;
51 }
52 var representativesToMerge = new HashSet<>();
53 Object bestRepresentative = null;
54 Set<Object> bestSet = null;
55 for (var object : toMerge) {
56 var representative = getRepresentative(object);
57 if (representativesToMerge.add(representative)) {
58 var component = getComponent(representative);
59 if (bestSet == null || bestSet.size() < component.size()) {
60 bestRepresentative = representative;
61 bestSet = component;
62 }
63 }
64 }
65 if (bestRepresentative == null) {
66 throw new AssertionError("Could not determine best representative");
67 }
68 for (var representative : representativesToMerge) {
69 if (!bestRepresentative.equals(representative)) {
70 components.remove(representative);
71 }
72 }
73 components.put(bestRepresentative, toMerge);
74 for (var object : toMerge) {
75 var previousRepresentative = representatives.put(object, bestRepresentative);
76 if (!bestSet.contains(object)) {
77 notifyToObservers(object, previousRepresentative, bestRepresentative);
78 }
79 }
80 }
81
82 protected void merge(Object leftRepresentative, Object rightRepresentative) {
83 if (leftRepresentative.equals(rightRepresentative)) {
84 return;
85 }
86 var leftSet = getComponent(leftRepresentative);
87 var rightSet = getComponent(rightRepresentative);
88 if (leftSet.size() < rightSet.size()) {
89 merge(rightRepresentative, rightSet, leftRepresentative, leftSet);
90 } else {
91 merge(leftRepresentative, leftSet, rightRepresentative, rightSet);
92 }
93 }
94
95 private void merge(Object preservedRepresentative, Set<Object> preservedSet, Object removedRepresentative,
96 Set<Object> removedSet) {
97 components.remove(removedRepresentative);
98 for (var node : removedSet) {
99 representatives.put(node, preservedRepresentative);
100 preservedSet.add(node);
101 notifyToObservers(node, removedRepresentative, preservedRepresentative);
102 }
103 }
104
105 protected void assignNewRepresentative(Object oldRepresentative, Set<Object> set) {
106 var iterator = set.iterator();
107 if (!iterator.hasNext()) {
108 return;
109 }
110 var newRepresentative = iterator.next();
111 components.put(newRepresentative, set);
112 for (var node : set) {
113 var oldRepresentativeOfNode = representatives.put(node, newRepresentative);
114 if (!oldRepresentative.equals(oldRepresentativeOfNode)) {
115 throw new IllegalArgumentException("Node %s was not represented by %s but by %s"
116 .formatted(node, oldRepresentative, oldRepresentativeOfNode));
117 }
118 notifyToObservers(node, oldRepresentative, newRepresentative);
119 }
120 }
121
122 public void setObserver(RepresentativeObserver observer) {
123 this.observer = observer;
124 }
125
126 public Map<Object, Set<Object>> getComponents() {
127 return components;
128 }
129
130 public Object getRepresentative(Object node) {
131 return representatives.get(node);
132 }
133
134 public Set<Object> getComponent(Object representative) {
135 return components.get(representative);
136 }
137
138 public void dispose() {
139 graph.detachObserver(this);
140 }
141
142 @Override
143 public void nodeInserted(Object n) {
144 var component = new HashSet<>(1);
145 component.add(n);
146 initializeSet(component);
147 notifyToObservers(n, n, Direction.INSERT);
148 }
149
150 @Override
151 public void nodeDeleted(Object n) {
152 var representative = representatives.remove(n);
153 if (!representative.equals(n)) {
154 throw new IllegalStateException("Trying to delete node with dangling edges");
155 }
156 components.remove(representative);
157 notifyToObservers(n, representative, Direction.DELETE);
158 }
159
160 protected void notifyToObservers(Object node, Object oldRepresentative, Object newRepresentative) {
161 notifyToObservers(node, oldRepresentative, Direction.DELETE);
162 notifyToObservers(node, newRepresentative, Direction.INSERT);
163 }
164
165 protected void notifyToObservers(Object node, Object representative, Direction direction) {
166 if (observer != null) {
167 observer.tupleChanged(node, representative, direction);
168 }
169 }
170
171 public interface Factory {
172 RepresentativeElectionAlgorithm create(Graph<Object> graph);
173 }
174}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeObserver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeObserver.java
new file mode 100644
index 00000000..6b772fa8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/RepresentativeObserver.java
@@ -0,0 +1,12 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.rete.itc.alg.representative;
7
8import tools.refinery.viatra.runtime.matchers.util.Direction;
9
10public interface RepresentativeObserver {
11 void tupleChanged(Object node, Object representative, Direction direction);
12}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java
new file mode 100644
index 00000000..0463301b
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java
@@ -0,0 +1,69 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.rete.itc.alg.representative;
7
8import tools.refinery.viatra.runtime.rete.itc.alg.misc.GraphHelper;
9import tools.refinery.viatra.runtime.rete.itc.alg.misc.bfs.BFS;
10import tools.refinery.viatra.runtime.rete.itc.alg.misc.scc.SCC;
11import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
12
13import java.util.Collection;
14import java.util.Set;
15
16public class StronglyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm {
17 public StronglyConnectedComponentAlgorithm(Graph<Object> graph) {
18 super(graph);
19 }
20
21 @Override
22 protected void initializeComponents() {
23 var computedSCCs = SCC.computeSCC(graph).getSccs();
24 for (var computedSCC : computedSCCs) {
25 initializeSet(computedSCC);
26 }
27 }
28
29 @Override
30 public void edgeInserted(Object source, Object target) {
31 var sourceRoot = getRepresentative(source);
32 var targetRoot = getRepresentative(target);
33 if (sourceRoot.equals(targetRoot)) {
34 // New edge does not change strongly connected components.
35 return;
36 }
37 if (BFS.isReachable(target, source, graph)) {
38 var sources = BFS.reachableSources(graph, target);
39 var targets = BFS.reachableTargets(graph, source);
40 sources.retainAll(targets);
41 merge(sources);
42 }
43 }
44
45 @Override
46 public void edgeDeleted(Object source, Object target) {
47 var sourceRoot = getRepresentative(source);
48 var targetRoot = getRepresentative(target);
49 if (!sourceRoot.equals(targetRoot)) {
50 // New edge does not change strongly connected components.
51 return;
52 }
53 var component = GraphHelper.getSubGraph(getComponent(sourceRoot), graph);
54 if (!BFS.isReachable(source, target, component)) {
55 var newSCCs = SCC.computeSCC(component).getSccs();
56 split(sourceRoot, newSCCs);
57 }
58 }
59
60 private void split(Object preservedRepresentative, Collection<? extends Set<Object>> sets) {
61 for (var set : sets) {
62 if (set.contains(preservedRepresentative)) {
63 components.put(preservedRepresentative, set);
64 } else {
65 assignNewRepresentative(preservedRepresentative, set);
66 }
67 }
68 }
69}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java
new file mode 100644
index 00000000..704f0235
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java
@@ -0,0 +1,85 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.rete.itc.alg.representative;
7
8import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
9
10import java.util.ArrayDeque;
11import java.util.HashSet;
12import java.util.Set;
13
14public class WeaklyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm {
15 public WeaklyConnectedComponentAlgorithm(Graph<Object> graph) {
16 super(graph);
17 }
18
19 @Override
20 protected void initializeComponents() {
21 for (var node : graph.getAllNodes()) {
22 if (representatives.containsKey(node)) {
23 continue;
24 }
25 var reachable = getReachableNodes(node);
26 initializeSet(reachable);
27 }
28 }
29
30 @Override
31 public void edgeInserted(Object source, Object target) {
32 var sourceRoot = getRepresentative(source);
33 var targetRoot = getRepresentative(target);
34 merge(sourceRoot, targetRoot);
35 }
36
37 @Override
38 public void edgeDeleted(Object source, Object target) {
39 var sourceRoot = getRepresentative(source);
40 var targetRoot = getRepresentative(target);
41 if (!sourceRoot.equals(targetRoot)) {
42 throw new IllegalArgumentException("Trying to remove edge not in graph");
43 }
44 var targetReachable = getReachableNodes(target);
45 if (!targetReachable.contains(source)) {
46 split(sourceRoot, targetReachable);
47 }
48 }
49
50 private void split(Object sourceRepresentative, Set<Object> targetReachable) {
51 var sourceComponent = getComponent(sourceRepresentative);
52 sourceComponent.removeAll(targetReachable);
53 if (targetReachable.contains(sourceRepresentative)) {
54 components.put(sourceRepresentative, targetReachable);
55 assignNewRepresentative(sourceRepresentative, sourceComponent);
56 } else {
57 assignNewRepresentative(sourceRepresentative, targetReachable);
58 }
59 }
60
61 private Set<Object> getReachableNodes(Object source) {
62 var retSet = new HashSet<>();
63 retSet.add(source);
64 var nodeQueue = new ArrayDeque<>();
65 nodeQueue.addLast(source);
66
67 while (!nodeQueue.isEmpty()) {
68 var node = nodeQueue.removeFirst();
69 for (var neighbor : graph.getTargetNodes(node).distinctValues()) {
70 if (!retSet.contains(neighbor)) {
71 retSet.add(neighbor);
72 nodeQueue.addLast(neighbor);
73 }
74 }
75 for (var neighbor : graph.getSourceNodes(node).distinctValues()) {
76 if (!retSet.contains(neighbor)) {
77 retSet.add(neighbor);
78 nodeQueue.addLast(neighbor);
79 }
80 }
81 }
82
83 return retSet;
84 }
85}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/util/CollectionHelper.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/util/CollectionHelper.java
new file mode 100644
index 00000000..6655be6d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/alg/util/CollectionHelper.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.alg.util;
10
11import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
12
13import java.util.Set;
14
15/**
16 * @author Tamas Szabo
17 *
18 */
19public class CollectionHelper {
20
21 private CollectionHelper() {/*Utility class constructor*/}
22
23 /**
24 * Returns the intersection of two sets. It calls {@link Set#retainAll(java.util.Collection)} but returns a new set
25 * containing the elements of the intersection.
26 *
27 * @param set1
28 * the first set (can be null, interpreted as empty)
29 * @param set2
30 * the second set (can be null, interpreted as empty)
31 * @return the intersection of the sets
32 * @since 1.7
33 */
34 public static <V> Set<V> intersection(Set<V> set1, Set<V> set2) {
35 if (set1 == null || set2 == null)
36 return CollectionsFactory.createSet();
37
38 Set<V> intersection = CollectionsFactory.createSet(set1);
39 intersection.retainAll(set2);
40 return intersection;
41 }
42
43
44 /**
45 * Returns the difference of two sets (S1\S2). It calls {@link Set#removeAll(java.util.Collection)} but returns a
46 * new set containing the elements of the difference.
47 *
48 * @param set1
49 * the first set (can be null, interpreted as empty)
50 * @param set2
51 * the second set (can be null, interpreted as empty)
52 * @return the difference of the sets
53 * @since 1.7
54 */
55 public static <V> Set<V> difference(Set<V> set1, Set<V> set2) {
56 if (set1 == null)
57 return CollectionsFactory.createSet();
58
59 Set<V> difference = CollectionsFactory.createSet(set1);
60 if (set2 != null) difference.removeAll(set2);
61 return difference;
62 }
63
64}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/DotGenerator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/DotGenerator.java
new file mode 100644
index 00000000..f7f6b5ed
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/DotGenerator.java
@@ -0,0 +1,160 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.itc.graphimpl;
10
11import tools.refinery.viatra.runtime.rete.itc.alg.misc.scc.SCC;
12import tools.refinery.viatra.runtime.rete.itc.alg.misc.scc.SCCResult;
13import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
14
15import java.util.HashMap;
16import java.util.Map;
17import java.util.Set;
18import java.util.function.Function;
19
20/**
21 * This class contains utility methods to generate dot representations for {@link Graph} instances.
22 *
23 * @author Tamas Szabo
24 * @since 2.3
25 */
26public class DotGenerator {
27
28 private static final String[] colors = new String[] { "yellow", "blue", "red", "green", "gray", "cyan" };
29
30 private DotGenerator() {
31
32 }
33
34 /**
35 * Generates the dot representation for the given graph.
36 *
37 * @param graph
38 * the graph
39 * @param colorSCCs
40 * specifies if the strongly connected components with size greater than shall be colored
41 * @param nameFunction
42 * use this function to provide custom names to nodes, null if the default toString shall be used
43 * @param colorFunction
44 * use this function to provide custom color to nodes, null if the default white color shall be used
45 * @param edgeFunction
46 * use this function to provide custom edge labels, null if no edge label shall be printed
47 * @return the dot representation as a string
48 */
49 public static <V> String generateDot(final Graph<V> graph, final boolean colorSCCs,
50 final Function<V, String> nameFunction, final Function<V, String> colorFunction,
51 final Function<V, Function<V, String>> edgeFunction) {
52 final Map<V, String> colorMap = new HashMap<V, String>();
53
54 if (colorSCCs) {
55 final SCCResult<V> result = SCC.computeSCC(graph);
56 final Set<Set<V>> sccs = result.getSccs();
57
58 int i = 0;
59 for (final Set<V> scc : sccs) {
60 if (scc.size() > 1) {
61 for (final V node : scc) {
62 final String color = colorMap.get(node);
63 if (color == null) {
64 colorMap.put(node, colors[i % colors.length]);
65 } else {
66 colorMap.put(node, colorMap.get(node) + ":" + colors[i % colors.length]);
67 }
68 }
69 i++;
70 }
71 }
72
73 // if a node has no color yet, then make it white
74 for (final V node : graph.getAllNodes()) {
75 if (!colorMap.containsKey(node)) {
76 colorMap.put(node, "white");
77 }
78 }
79 } else {
80 for (final V node : graph.getAllNodes()) {
81 colorMap.put(node, "white");
82 }
83 }
84
85 if (colorFunction != null) {
86 for (final V node : graph.getAllNodes()) {
87 colorMap.put(node, colorFunction.apply(node));
88 }
89 }
90
91 final StringBuilder builder = new StringBuilder();
92 builder.append("digraph g {\n");
93
94 for (final V node : graph.getAllNodes()) {
95 final String nodePresentation = nameFunction == null ? node.toString() : nameFunction.apply(node);
96 builder.append("\"" + nodePresentation + "\"");
97 builder.append("[style=filled,fillcolor=" + colorMap.get(node) + "]");
98 builder.append(";\n");
99 }
100
101 for (final V source : graph.getAllNodes()) {
102 final IMemoryView<V> targets = graph.getTargetNodes(source);
103 if (!targets.isEmpty()) {
104 final String sourcePresentation = nameFunction == null ? source.toString() : nameFunction.apply(source);
105 for (final V target : targets.distinctValues()) {
106 String edgeLabel = null;
107 if (edgeFunction != null) {
108 final Function<V, String> v1 = edgeFunction.apply(source);
109 if (v1 != null) {
110 edgeLabel = v1.apply(target);
111 }
112 }
113
114 final String targetPresentation = nameFunction == null ? target.toString()
115 : nameFunction.apply(target);
116
117 builder.append("\"" + sourcePresentation + "\" -> \"" + targetPresentation + "\"");
118 if (edgeLabel != null) {
119 builder.append("[label=\"" + edgeLabel + "\"]");
120 }
121 builder.append(";\n");
122 }
123 }
124 }
125
126 builder.append("}");
127 return builder.toString();
128 }
129
130 /**
131 * Generates the dot representation for the given graph. No special pretty printing customization will be applied.
132 *
133 * @param graph
134 * the graph
135 * @return the dot representation as a string
136 */
137 public static <V> String generateDot(final Graph<V> graph) {
138 return generateDot(graph, false, null, null, null);
139 }
140
141 /**
142 * Returns a simple name shortener function that can be used in the graphviz visualization to help with readability.
143 * WARNING: if you shorten the name of the {@link Node}s too much, the visualization may become incorrect because
144 * grahpviz will treat different nodes as the same if their shortened names are the same.
145 *
146 * @param maxLength
147 * the maximum length of the text that is kept from the toString of the objects in the graph
148 * @return the shrunk toString value
149 */
150 public static <V> Function<V, String> getNameShortener(final int maxLength) {
151 return new Function<V, String>() {
152 @Override
153 public String apply(final V obj) {
154 final String value = obj.toString();
155 return value.substring(0, Math.min(value.length(), maxLength));
156 }
157 };
158 }
159
160}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/Graph.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/Graph.java
new file mode 100644
index 00000000..91604cb2
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/graphimpl/Graph.java
@@ -0,0 +1,185 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.graphimpl;
11
12import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphDataSource;
13import tools.refinery.viatra.runtime.rete.itc.igraph.IGraphObserver;
14import tools.refinery.viatra.runtime.rete.itc.igraph.IBiDirectionalGraphDataSource;
15import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
16import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
17import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
18import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
19
20import java.util.List;
21import java.util.Map;
22import java.util.Map.Entry;
23import java.util.Set;
24
25public class Graph<V> implements IGraphDataSource<V>, IBiDirectionalGraphDataSource<V> {
26
27 // source -> target -> count
28 private IMultiLookup<V, V> outgoingEdges;
29 // target -> source -> count
30 private IMultiLookup<V, V> incomingEdges;
31
32 private Set<V> nodes;
33
34 private List<IGraphObserver<V>> observers;
35
36 public Graph() {
37 outgoingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class);
38 incomingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class);
39 nodes = CollectionsFactory.createSet();
40 observers = CollectionsFactory.createObserverList();
41 }
42
43 public void insertEdge(V source, V target) {
44 outgoingEdges.addPair(source, target);
45 incomingEdges.addPair(target, source);
46
47 for (IGraphObserver<V> go : observers) {
48 go.edgeInserted(source, target);
49 }
50 }
51
52 /**
53 * No-op if trying to delete edge that does not exist
54 *
55 * @since 2.0
56 * @see #deleteEdgeIfExists(Object, Object)
57 */
58 public void deleteEdgeIfExists(V source, V target) {
59 boolean containedEdge = outgoingEdges.lookupOrEmpty(source).containsNonZero(target);
60 if (containedEdge) {
61 deleteEdgeThatExists(source, target);
62 }
63 }
64
65 /**
66 * @throws IllegalStateException
67 * if trying to delete edge that does not exist
68 * @since 2.0
69 * @see #deleteEdgeIfExists(Object, Object)
70 */
71 public void deleteEdgeThatExists(V source, V target) {
72 outgoingEdges.removePair(source, target);
73 incomingEdges.removePair(target, source);
74 for (IGraphObserver<V> go : observers) {
75 go.edgeDeleted(source, target);
76 }
77 }
78
79 /**
80 * @deprecated use explicitly {@link #deleteEdgeThatExists(Object, Object)} or
81 * {@link #deleteEdgeIfExists(Object, Object)} instead. To preserve backwards compatibility, this method
82 * delegates to the latter.
83 *
84 */
85 @Deprecated
86 public void deleteEdge(V source, V target) {
87 deleteEdgeIfExists(source, target);
88 }
89
90 /**
91 * Insert the given node into the graph.
92 */
93 public void insertNode(V node) {
94 if (nodes.add(node)) {
95 for (IGraphObserver<V> go : observers) {
96 go.nodeInserted(node);
97 }
98 }
99 }
100
101 /**
102 * Deletes the given node AND all of the edges going in and out from the node.
103 */
104 public void deleteNode(V node) {
105 if (nodes.remove(node)) {
106 IMemoryView<V> incomingView = incomingEdges.lookup(node);
107 if (incomingView != null) {
108 Map<V, Integer> incoming = CollectionsFactory.createMap(incomingView.asMap());
109
110 for (Entry<V, Integer> entry : incoming.entrySet()) {
111 for (int i = 0; i < entry.getValue(); i++) {
112 deleteEdgeThatExists(entry.getKey(), node);
113 }
114 }
115 }
116
117 IMemoryView<V> outgoingView = outgoingEdges.lookup(node);
118 if (outgoingView != null) {
119 Map<V, Integer> outgoing = CollectionsFactory.createMap(outgoingView.asMap());
120
121 for (Entry<V, Integer> entry : outgoing.entrySet()) {
122 for (int i = 0; i < entry.getValue(); i++) {
123 deleteEdgeThatExists(node, entry.getKey());
124 }
125 }
126 }
127
128 for (IGraphObserver<V> go : observers) {
129 go.nodeDeleted(node);
130 }
131 }
132 }
133
134 @Override
135 public void attachObserver(IGraphObserver<V> go) {
136 observers.add(go);
137 }
138
139 @Override
140 public void attachAsFirstObserver(IGraphObserver<V> observer) {
141 observers.add(0, observer);
142 }
143
144 @Override
145 public void detachObserver(IGraphObserver<V> go) {
146 observers.remove(go);
147 }
148
149 @Override
150 public Set<V> getAllNodes() {
151 return nodes;
152 }
153
154 @Override
155 public IMemoryView<V> getTargetNodes(V source) {
156 return outgoingEdges.lookupOrEmpty(source);
157 }
158
159 @Override
160 public IMemoryView<V> getSourceNodes(V target) {
161 return incomingEdges.lookupOrEmpty(target);
162 }
163
164 @Override
165 public String toString() {
166 StringBuilder sb = new StringBuilder();
167 sb.append("nodes = ");
168 for (V n : getAllNodes()) {
169 sb.append(n.toString());
170 sb.append(" ");
171 }
172 sb.append(" edges = ");
173 for (V source : outgoingEdges.distinctKeys()) {
174 IMemoryView<V> targets = outgoingEdges.lookup(source);
175 for (V target : targets.distinctValues()) {
176 int count = targets.getCount(target);
177 for (int i = 0; i < count; i++) {
178 sb.append("(" + source + "," + target + ") ");
179 }
180 }
181 }
182 return sb.toString();
183 }
184
185}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalGraphDataSource.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalGraphDataSource.java
new file mode 100644
index 00000000..4fcaa71f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalGraphDataSource.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
13import tools.refinery.viatra.runtime.matchers.util.IMultiset;
14
15/**
16 * A bi-directional graph data source supports all operations that an {@link IGraphDataSource} does, but it
17 * also makes it possible to query the incoming edges of nodes, not only the outgoing edges.
18 *
19 * @author Tamas Szabo
20 *
21 * @param <V> the type of the nodes in the graph
22 */
23public interface IBiDirectionalGraphDataSource<V> extends IGraphDataSource<V> {
24
25 /**
26 * Returns the source nodes for the given target node.
27 * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source.
28 *
29 * The method must not return null.
30 *
31 * @param target the target node
32 * @return the multiset of source nodes
33 * @since 2.0
34 */
35 public IMemoryView<V> getSourceNodes(V target);
36
37}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalWrapper.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalWrapper.java
new file mode 100644
index 00000000..c4315ca2
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IBiDirectionalWrapper.java
@@ -0,0 +1,110 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
15import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
16import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
17import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
18
19/**
20 * This class can be used to wrap an {@link IGraphDataSource} into an {@link IBiDirectionalGraphDataSource}. This class
21 * provides support for the retrieval of source nodes for a given target which is not supported by standard
22 * {@link IGraphDataSource} implementations.
23 *
24 * @author Tamas Szabo
25 *
26 * @param <V>
27 * the type parameter of the nodes in the graph data source
28 */
29public class IBiDirectionalWrapper<V> implements IBiDirectionalGraphDataSource<V>, IGraphObserver<V> {
30
31 private IGraphDataSource<V> wrappedDataSource;
32 // target -> source -> count
33 private IMultiLookup<V, V> incomingEdges;
34
35 public IBiDirectionalWrapper(IGraphDataSource<V> gds) {
36 this.wrappedDataSource = gds;
37
38 this.incomingEdges = CollectionsFactory.createMultiLookup(
39 Object.class, MemoryType.MULTISETS, Object.class);
40
41 if (gds.getAllNodes() != null) {
42 for (V source : gds.getAllNodes()) {
43 IMemoryView<V> targets = gds.getTargetNodes(source);
44 for (V target : targets.distinctValues()) {
45 int count = targets.getCount(target);
46 for (int i = 0; i < count; i++) {
47 edgeInserted(source, target);
48 }
49 }
50 }
51 }
52
53 gds.attachAsFirstObserver(this);
54 }
55
56 @Override
57 public void attachObserver(IGraphObserver<V> observer) {
58 wrappedDataSource.attachObserver(observer);
59 }
60
61 @Override
62 public void attachAsFirstObserver(IGraphObserver<V> observer) {
63 wrappedDataSource.attachAsFirstObserver(observer);
64 }
65
66 @Override
67 public void detachObserver(IGraphObserver<V> observer) {
68 wrappedDataSource.detachObserver(observer);
69 }
70
71 @Override
72 public Set<V> getAllNodes() {
73 return wrappedDataSource.getAllNodes();
74 }
75
76 @Override
77 public IMemoryView<V> getTargetNodes(V source) {
78 return wrappedDataSource.getTargetNodes(source);
79 }
80
81 @Override
82 public IMemoryView<V> getSourceNodes(V target) {
83 return incomingEdges.lookupOrEmpty(target);
84 }
85
86 @Override
87 public void edgeInserted(V source, V target) {
88 incomingEdges.addPair(target, source);
89 }
90
91 @Override
92 public void edgeDeleted(V source, V target) {
93 incomingEdges.removePair(target, source);
94 }
95
96 @Override
97 public void nodeInserted(V n) {
98
99 }
100
101 @Override
102 public void nodeDeleted(V node) {
103
104 }
105
106 @Override
107 public String toString() {
108 return wrappedDataSource.toString();
109 }
110}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphDataSource.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphDataSource.java
new file mode 100644
index 00000000..9159a692
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphDataSource.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
13import tools.refinery.viatra.runtime.matchers.util.IMultiset;
14
15import java.util.Set;
16
17/**
18 * The interface prescribes the set of operations that a graph data source must support.
19 * <p> Note that the old version of the interface is broken at version 1.6;
20 * MultiSets are now presented as Maps instead of Lists.
21 *
22 * @author Tamas Szabo
23 *
24 * @param <V>
25 * the type of the nodes in the graph
26 */
27public interface IGraphDataSource<V> {
28
29 /**
30 * Attaches a new graph observer to this graph data source. Observers will be notified in the order they have been registered.
31 *
32 * @param observer the graph observer
33 */
34 public void attachObserver(IGraphObserver<V> observer);
35
36 /**
37 * Attaches a new graph observer to this graph data source as the first one.
38 * In the notification order this observer will be the first one as long as another call to this method happens.
39 *
40 * @param observer the graph observer
41 * @since 1.6
42 */
43 public void attachAsFirstObserver(IGraphObserver<V> observer);
44
45 /**
46 * Detaches an already registered graph observer from this graph data source.
47 *
48 * @param observer the graph observer
49 */
50 public void detachObserver(IGraphObserver<V> observer);
51
52 /**
53 * Returns the complete set of nodes in the graph data source.
54 *
55 * @return the set of all nodes
56 */
57 public Set<V> getAllNodes();
58
59 /**
60 * Returns the target nodes for the given source node.
61 * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source.
62 *
63 * The method must not return null.
64 *
65 * @param source the source node
66 * @return the multiset of target nodes
67 * @since 2.0
68 */
69 public IMemoryView<V> getTargetNodes(V source);
70}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphObserver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphObserver.java
new file mode 100644
index 00000000..a282216d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/IGraphObserver.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12/**
13 * Interface GraphObserver is used to observ the changes in a graph; edge and node insertion/deleteion.
14 *
15 * @author Tamas Szabo
16 *
17 */
18public interface IGraphObserver<V> {
19
20 /**
21 * Used to notify when an edge is inserted into the graph.
22 *
23 * @param source
24 * the source of the edge
25 * @param target
26 * the target of the edge
27 */
28 public void edgeInserted(V source, V target);
29
30 /**
31 * Used to notify when an edge is deleted from the graph.
32 *
33 * @param source
34 * the source of the edge
35 * @param target
36 * the target of the edge
37 */
38 public void edgeDeleted(V source, V target);
39
40 /**
41 * Used to notify when a node is inserted into the graph.
42 *
43 * @param n
44 * the node
45 */
46 public void nodeInserted(V n);
47
48 /**
49 * Used to notify when a node is deleted from the graph.
50 *
51 * @param n
52 * the node
53 */
54 public void nodeDeleted(V n);
55}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcDataSource.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcDataSource.java
new file mode 100644
index 00000000..5ede600f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcDataSource.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.rete.itc.alg.misc.IGraphPathFinder;
15
16/**
17 * This interface defines those methods that a transitive reachability data source should provide.
18 *
19 * @author Tamas Szabo
20 *
21 * @param <V>
22 * the type parameter of the node
23 */
24public interface ITcDataSource<V> {
25
26 /**
27 * Attach a transitive closure relation observer.
28 *
29 * @param to
30 * the observer object
31 */
32 public void attachObserver(ITcObserver<V> to);
33
34 /**
35 * Detach a transitive closure relation observer.
36 *
37 * @param to
38 * the observer object
39 */
40 public void detachObserver(ITcObserver<V> to);
41
42 /**
43 * Returns all nodes which are reachable from the source node.
44 *
45 * @param source
46 * the source node
47 * @return the set of target nodes
48 */
49 public Set<V> getAllReachableTargets(V source);
50
51 /**
52 * Returns all nodes from which the target node is reachable.
53 *
54 * @param target
55 * the target node
56 * @return the set of source nodes
57 */
58 public Set<V> getAllReachableSources(V target);
59
60 /**
61 * Returns true if the target node is reachable from the source node.
62 *
63 * @param source
64 * the source node
65 * @param target
66 * the target node
67 * @return true if target is reachable from source, false otherwise
68 */
69 public boolean isReachable(V source, V target);
70
71 /**
72 * The returned {@link IGraphPathFinder} can be used to retrieve paths between nodes using transitive reachability.
73 *
74 * @return a path finder for the graph.
75 */
76 public IGraphPathFinder<V> getPathFinder();
77
78 /**
79 * Call this method to properly dispose the data structures of a transitive closure algorithm.
80 */
81 public void dispose();
82}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcObserver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcObserver.java
new file mode 100644
index 00000000..74e0cb75
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/itc/igraph/ITcObserver.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.itc.igraph;
11
12/**
13 * Interface ITcObserver is used to observe the changes in a transitive closure relation; tuple insertion/deletion.
14 *
15 * @author Szabo Tamas
16 *
17 */
18public interface ITcObserver<V> {
19
20 /**
21 * Used to notify when a tuple is inserted into the transitive closure relation.
22 *
23 * @param source
24 * the source of the tuple
25 * @param target
26 * the target of the tuple
27 */
28 public void tupleInserted(V source, V target);
29
30 /**
31 * Used to notify when a tuple is deleted from the transitive closure relation.
32 *
33 * @param source
34 * the source of the tuple
35 * @param target
36 * the target of the tuple
37 */
38 public void tupleDeleted(V source, V target);
39}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java
new file mode 100644
index 00000000..83e86fe6
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
12import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
13
14/**
15 * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use delete and re-derive
16 * evaluation.
17 *
18 * @author Tamas Szabo
19 * @since 2.2
20 */
21public class DRedReteBackendFactory extends ReteBackendFactory {
22
23 public static final DRedReteBackendFactory INSTANCE = new DRedReteBackendFactory();
24
25 @Override
26 public IQueryBackend create(IQueryBackendContext context) {
27 return create(context, true, null);
28 }
29
30 @Override
31 public int hashCode() {
32 return DRedReteBackendFactory.class.hashCode();
33 }
34
35 @Override
36 public boolean equals(final Object obj) {
37 if (this == obj) {
38 return true;
39 }
40 if (obj == null) {
41 return false;
42 }
43 if (!(obj instanceof DRedReteBackendFactory)) {
44 return false;
45 }
46 return true;
47 }
48
49}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java
new file mode 100644
index 00000000..a4fa4914
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import java.util.HashMap;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
15import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17
18/**
19 * A configurable hint provider that gathers hints for queries during runtime, and delegates defaults to an external hint provider.
20 *
21 * @author Gabor Bergmann
22 * @since 1.5
23 */
24class HintConfigurator implements IQueryBackendHintProvider {
25
26 private IQueryBackendHintProvider defaultHintProvider;
27 private Map<PQuery, QueryEvaluationHint> storedHints = new HashMap<PQuery, QueryEvaluationHint>();
28
29 public HintConfigurator(IQueryBackendHintProvider defaultHintProvider) {
30 this.defaultHintProvider = defaultHintProvider;
31 }
32
33 @Override
34 public QueryEvaluationHint getQueryEvaluationHint(PQuery query) {
35 return defaultHintProvider.getQueryEvaluationHint(query).overrideBy(storedHints.get(query));
36 }
37
38 public void storeHint(PQuery query, QueryEvaluationHint hint) {
39 QueryEvaluationHint oldHint = storedHints.get(query);
40 if (oldHint == null)
41 storedHints.put(query, hint);
42 else
43 storedHints.put(query, oldHint.overrideBy(hint));
44 }
45
46}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java
new file mode 100644
index 00000000..4c64a1a1
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
12
13/**
14 * @author Grill Balázs
15 * @since 1.4
16 *
17 */
18public class IncrementalMatcherCapability implements IMatcherCapability {
19
20 @Override
21 public boolean canBeSubstitute(IMatcherCapability capability) {
22 /*
23 * TODO: for now, as we are only prepared for Rete and LS, we can assume that
24 * a matcher created with Rete can always be a substitute for a matcher created
25 * by any backend.
26 */
27 return true;
28 }
29
30}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java
new file mode 100644
index 00000000..347cabc4
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
13import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
14import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
15import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
16import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler;
19import tools.refinery.viatra.runtime.rete.util.Options;
20
21public class ReteBackendFactory implements IQueryBackendFactory {
22 /**
23 * EXPERIMENTAL
24 */
25 protected static final int reteThreads = 0;
26
27 /**
28 * @since 2.0
29 */
30 public static final ReteBackendFactory INSTANCE = new ReteBackendFactory();
31
32 /**
33 * @deprecated Use the static {@link #INSTANCE} field instead
34 */
35 @Deprecated
36 public ReteBackendFactory() {
37 }
38
39 /**
40 * @since 1.5
41 */
42 @Override
43 public IQueryBackend create(IQueryBackendContext context) {
44 return create(context, false, null);
45 }
46
47 /**
48 * @since 2.4
49 */
50 public IQueryBackend create(IQueryBackendContext context, boolean deleteAndRederiveEvaluation,
51 TimelyConfiguration timelyConfiguration) {
52 ReteEngine engine;
53 engine = new ReteEngine(context, reteThreads, deleteAndRederiveEvaluation, timelyConfiguration);
54 IQueryBackendHintProvider hintConfiguration = engine.getHintConfiguration();
55 ReteRecipeCompiler compiler = new ReteRecipeCompiler(
56 Options.builderMethod.layoutStrategy(context, hintConfiguration), context.getLogger(),
57 context.getRuntimeContext().getMetaContext(), context.getQueryCacheContext(), hintConfiguration,
58 context.getQueryAnalyzer(), deleteAndRederiveEvaluation, timelyConfiguration);
59 engine.setCompiler(compiler);
60 return engine;
61 }
62
63 @Override
64 public Class<? extends IQueryBackend> getBackendClass() {
65 return ReteEngine.class;
66 }
67
68 @Override
69 public int hashCode() {
70 return ReteBackendFactory.class.hashCode();
71 }
72
73 @Override
74 public boolean equals(Object obj) {
75 if (this == obj) {
76 return true;
77 }
78 if (obj == null) {
79 return false;
80 }
81 if (!(obj instanceof ReteBackendFactory)) {
82 return false;
83 }
84 return true;
85 }
86
87 /**
88 * @since 1.4
89 */
90 @Override
91 public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) {
92 return new IncrementalMatcherCapability();
93 }
94
95 @Override
96 public boolean isCaching() {
97 return true;
98 }
99
100} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java
new file mode 100644
index 00000000..98775ab3
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider;
13
14/**
15 * @since 2.0
16 *
17 */
18public class ReteBackendFactoryProvider implements IQueryBackendFactoryProvider {
19
20 @Override
21 public IQueryBackendFactory getFactory() {
22 return ReteBackendFactory.INSTANCE;
23 }
24
25 @Override
26 public boolean isSystemDefaultEngine() {
27 return true;
28 }
29
30 @Override
31 public boolean isSystemDefaultCachingBackend() {
32 return true;
33 }
34
35}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java
new file mode 100644
index 00000000..9bd499f4
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java
@@ -0,0 +1,579 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.matcher;
11
12import java.lang.reflect.InvocationTargetException;
13import java.util.Collection;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.concurrent.Callable;
17
18import org.apache.log4j.Logger;
19import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
20import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
21import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
22import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
23import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
24import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
25import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
26import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
27import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
28import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
29import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
30import tools.refinery.viatra.runtime.rete.boundary.Disconnectable;
31import tools.refinery.viatra.runtime.rete.boundary.ReteBoundary;
32import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException;
33import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler;
34import tools.refinery.viatra.runtime.rete.index.Indexer;
35import tools.refinery.viatra.runtime.rete.network.Network;
36import tools.refinery.viatra.runtime.rete.network.NodeProvisioner;
37import tools.refinery.viatra.runtime.rete.network.ReteContainer;
38import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
39
40/**
41 * @author Gabor Bergmann
42 *
43 */
44public class ReteEngine implements IQueryBackend {
45
46 protected Network reteNet;
47 protected final int reteThreads;
48 protected ReteBoundary boundary;
49
50 /**
51 * @since 2.2
52 */
53 protected final boolean deleteAndRederiveEvaluation;
54 /**
55 * @since 2.4
56 */
57 protected final TimelyConfiguration timelyConfiguration;
58
59 private IQueryBackendContext context;
60 private Logger logger;
61 protected IQueryRuntimeContext runtimeContext;
62
63 protected Collection<Disconnectable> disconnectables;
64
65 protected Map<PQuery, RetePatternMatcher> matchers;
66
67 protected ReteRecipeCompiler compiler;
68
69 protected final boolean parallelExecutionEnabled; // TRUE if model manipulation can go on
70
71 private boolean disposedOrUninitialized = true;
72
73 private HintConfigurator hintConfigurator;
74
75 /**
76 * @param context
77 * the context of the pattern matcher, conveying all information from the outside world.
78 * @param reteThreads
79 * the number of threads to operate the RETE network with; 0 means single-threaded operation, 1 starts an
80 * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers.
81 */
82 public ReteEngine(IQueryBackendContext context, int reteThreads) {
83 this(context, reteThreads, false, null);
84 }
85
86 /**
87 * @since 2.4
88 */
89 public ReteEngine(IQueryBackendContext context, int reteThreads, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyConfiguration) {
90 super();
91 this.context = context;
92 this.logger = context.getLogger();
93 this.runtimeContext = context.getRuntimeContext();
94 this.reteThreads = reteThreads;
95 this.parallelExecutionEnabled = reteThreads > 0;
96 this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation;
97 this.timelyConfiguration = timelyConfiguration;
98 initEngine();
99 this.compiler = null;
100 }
101
102 /**
103 * @since 1.6
104 */
105 public IQueryBackendContext getBackendContext() {
106 return context;
107 }
108
109 /**
110 * @since 2.2
111 */
112 public boolean isDeleteAndRederiveEvaluation() {
113 return this.deleteAndRederiveEvaluation;
114 }
115
116 /**
117 * @since 2.4
118 */
119 public TimelyConfiguration getTimelyConfiguration() {
120 return this.timelyConfiguration;
121 }
122
123 /**
124 * initializes engine components
125 */
126 private synchronized void initEngine() {
127 this.disposedOrUninitialized = false;
128 this.disconnectables = new LinkedList<Disconnectable>();
129 // this.caughtExceptions = new LinkedBlockingQueue<Throwable>();
130
131
132 this.hintConfigurator = new HintConfigurator(context.getHintProvider());
133
134 this.reteNet = new Network(reteThreads, this);
135 this.boundary = new ReteBoundary(this); // prerequisite: network
136
137 this.matchers = CollectionsFactory.createMap();
138 /* this.matchersScoped = new HashMap<PatternDescription, Map<Map<Integer,Scope>,RetePatternMatcher>>(); */
139
140 // prerequisite: network, framework, boundary, disconnectables
141 //context.subscribeBackendForUpdates(this.boundary);
142 // prerequisite: boundary, disconnectables
143// this.traceListener = context.subscribePatternMatcherForTraceInfluences(this);
144
145 }
146
147 @Override
148 public void flushUpdates() {
149 for (ReteContainer container : this.reteNet.getContainers()) {
150 container.deliverMessagesSingleThreaded();
151 }
152 }
153
154 /**
155 * deconstructs engine components
156 */
157 private synchronized void deconstructEngine() {
158 ensureInitialized();
159 reteNet.kill();
160
161 //context.unSubscribeBackendFromUpdates(this.boundary);
162 for (Disconnectable disc : disconnectables) {
163 disc.disconnect();
164 }
165
166 this.matchers = null;
167 this.disconnectables = null;
168
169 this.reteNet = null;
170 this.boundary = null;
171
172 this.hintConfigurator = null;
173
174 // this.machineListener = new MachineListener(this); // prerequisite:
175 // framework, disconnectables
176// this.traceListener = null;
177
178 this.disposedOrUninitialized = true;
179 }
180
181 /**
182 * Deconstructs the engine to get rid of it finally
183 */
184 public void killEngine() {
185 deconstructEngine();
186 // this.framework = null;
187 this.compiler = null;
188 this.logger = null;
189 }
190
191 /**
192 * Resets the engine to an after-initialization phase
193 *
194 */
195 public void reset() {
196 deconstructEngine();
197
198 initEngine();
199
200 compiler.reset();
201 }
202
203 /**
204 * Accesses the patternmatcher for a given pattern, constructs one if a matcher is not available yet.
205 *
206 * @pre: builder is set.
207 * @param query
208 * the pattern to be matched.
209 * @return a patternmatcher object that can match occurences of the given pattern.
210 * @throws ViatraQueryRuntimeException
211 * if construction fails.
212 */
213 public synchronized RetePatternMatcher accessMatcher(final PQuery query) {
214 ensureInitialized();
215 RetePatternMatcher matcher;
216 // String namespace = gtPattern.getNamespace().getName();
217 // String name = gtPattern.getName();
218 // String fqn = namespace + "." + name;
219 matcher = matchers.get(query);
220 if (matcher == null) {
221 constructionWrapper(() -> {
222 RecipeTraceInfo prodNode;
223 prodNode = boundary.accessProductionTrace(query);
224
225 RetePatternMatcher retePatternMatcher = new RetePatternMatcher(ReteEngine.this,
226 prodNode);
227 retePatternMatcher.setTag(query);
228 matchers.put(query, retePatternMatcher);
229 return null;
230 });
231 matcher = matchers.get(query);
232 }
233
234 executeDelayedCommands();
235
236 return matcher;
237 }
238
239
240 /**
241 * Constructs RETE pattern matchers for a collection of patterns, if they are not available yet. Model traversal
242 * during the whole construction period is coalesced (which may have an effect on performance, depending on the
243 * matcher context).
244 *
245 * @pre: builder is set.
246 * @param specifications
247 * the patterns to be matched.
248 * @throws ViatraQueryRuntimeException
249 * if construction fails.
250 */
251 public synchronized void buildMatchersCoalesced(final Collection<PQuery> specifications) {
252 ensureInitialized();
253 constructionWrapper(() -> {
254 for (PQuery specification : specifications) {
255 boundary.accessProductionNode(specification);
256 }
257 return null;
258 });
259 }
260
261 /**
262 * @since 2.4
263 */
264 public <T> T constructionWrapper(final Callable<T> payload) {
265 T result = null;
266// context.modelReadLock();
267// try {
268 if (parallelExecutionEnabled)
269 reteNet.getStructuralChangeLock().lock();
270 try {
271 try {
272 result = runtimeContext.coalesceTraversals(() -> {
273 T innerResult = payload.call();
274 this.executeDelayedCommands();
275 return innerResult;
276 });
277 } catch (InvocationTargetException ex) {
278 final Throwable cause = ex.getCause();
279 if (cause instanceof RetePatternBuildException)
280 throw (RetePatternBuildException) cause;
281 if (cause instanceof RuntimeException)
282 throw (RuntimeException) cause;
283 assert (false);
284 }
285 } finally {
286 if (parallelExecutionEnabled)
287 reteNet.getStructuralChangeLock().unlock();
288 reteNet.waitForReteTermination();
289 }
290// } finally {
291// context.modelReadUnLock();
292// }
293 return result;
294 }
295
296 // /**
297 // * Accesses the patternmatcher for a given pattern with additional scoping, constructs one if
298 // * a matcher is not available yet.
299 // *
300 // * @param gtPattern
301 // * the pattern to be matched.
302 // * @param additionalScopeMap
303 // * additional, optional scopes for the symbolic parameters
304 // * maps the position of the symbolic parameter to its additional scope (if any)
305 // * @pre: scope.parent is non-root, i.e. this is a nontrivial constraint
306 // * use the static method RetePatternMatcher.buildAdditionalScopeMap() to create from PatternCallSignature
307 // * @return a patternmatcher object that can match occurences of the given
308 // * pattern.
309 // * @throws PatternMatcherCompileTimeException
310 // * if construction fails.
311 // */
312 // public synchronized RetePatternMatcher accessMatcherScoped(PatternDescription gtPattern, Map<Integer, Scope>
313 // additionalScopeMap)
314 // throws PatternMatcherCompileTimeException {
315 // if (additionalScopeMap.isEmpty()) return accessMatcher(gtPattern);
316 //
317 // RetePatternMatcher matcher;
318 //
319 // Map<Map<Integer, Scope>, RetePatternMatcher> scopes = matchersScoped.get(gtPattern);
320 // if (scopes == null) {
321 // scopes = new HashMap<Map<Integer, Scope>, RetePatternMatcher>();
322 // matchersScoped.put(gtPattern, scopes);
323 // }
324 //
325 // matcher = scopes.get(additionalScopeMap);
326 // if (matcher == null) {
327 // context.modelReadLock();
328 // try {
329 // reteNet.getStructuralChangeLock().lock();
330 // try {
331 // Address<? extends Production> prodNode;
332 // prodNode = boundary.accessProductionScoped(gtPattern, additionalScopeMap);
333 //
334 // matcher = new RetePatternMatcher(this, prodNode);
335 // scopes.put(additionalScopeMap, matcher);
336 // } finally {
337 // reteNet.getStructuralChangeLock().unlock();
338 // }
339 // } finally {
340 // context.modelReadUnLock();
341 // }
342 // // reteNet.flushUpdates();
343 // }
344 //
345 // return matcher;
346 // }
347
348 /**
349 * Returns an indexer that groups the contents of this Production node by their projections to a given mask.
350 * Designed to be called by a RetePatternMatcher.
351 *
352 * @param production
353 * the production node to be indexed.
354 * @param mask
355 * the mask that defines the projection.
356 * @return the Indexer.
357 */
358 synchronized Indexer accessProjection(RecipeTraceInfo production, TupleMask mask) {
359 ensureInitialized();
360 NodeProvisioner nodeProvisioner = reteNet.getHeadContainer().getProvisioner();
361 Indexer result = nodeProvisioner.peekProjectionIndexer(production, mask);
362 if (result == null) {
363 result = constructionWrapper(() ->
364 nodeProvisioner.accessProjectionIndexerOnetime(production, mask)
365 );
366 }
367
368 return result;
369 }
370
371 // /**
372 // * Retrieves the patternmatcher for a given pattern fqn, returns null if
373 // the matching network hasn't been constructed yet.
374 // *
375 // * @param fqn the fully qualified name of the pattern to be matched.
376 // * @return the previously constructed patternmatcher object that can match
377 // occurences of the given pattern, or null if it doesn't exist.
378 // */
379 // public RetePatternMatcher getMatcher(String fqn)
380 // {
381 // RetePatternMatcher matcher = matchersByFqn.get(fqn);
382 // if (matcher == null)
383 // {
384 // Production prodNode = boundary.getProduction(fqn);
385 //
386 // matcher = new RetePatternMatcher(this, prodNode);
387 // matchersByFqn.put(fqn, matcher);
388 // }
389 //
390 // return matcher;
391 // }
392
393 /**
394 * @since 2.3
395 */
396 public void executeDelayedCommands() {
397 for (final ReteContainer container : this.reteNet.getContainers()) {
398 container.executeDelayedCommands();
399 }
400 }
401
402 /**
403 * Waits until the pattern matcher is in a steady state and output can be retrieved.
404 */
405 public void settle() {
406 ensureInitialized();
407 reteNet.waitForReteTermination();
408 }
409
410 /**
411 * Waits until the pattern matcher is in a steady state and output can be retrieved. When steady state is reached, a
412 * retrieval action is executed before the steady state ceases.
413 *
414 * @param action
415 * the action to be run when reaching the steady-state.
416 */
417 public void settle(Runnable action) {
418 ensureInitialized();
419 reteNet.waitForReteTermination(action);
420 }
421
422 // /**
423 // * @return the framework
424 // */
425 // public IFramework getFramework() {
426 // return framework.get();
427 // }
428
429 /**
430 * @return the reteNet
431 */
432 public Network getReteNet() {
433 ensureInitialized();
434 return reteNet;
435 }
436
437 /**
438 * @return the boundary
439 */
440 public ReteBoundary getBoundary() {
441 ensureInitialized();
442 return boundary;
443 }
444
445 // /**
446 // * @return the pattern matcher builder
447 // */
448 // public IRetePatternBuilder getBuilder() {
449 // return builder;
450 // }
451
452 /**
453 * @param builder
454 * the pattern matcher builder to set
455 */
456 public void setCompiler(ReteRecipeCompiler builder) {
457 ensureInitialized();
458 this.compiler = builder;
459 }
460
461// /**
462// * @return the manipulationListener
463// */
464// public IManipulationListener getManipulationListener() {
465// ensureInitialized();
466// return manipulationListener;
467// }
468
469// /**
470// * @return the traceListener
471// */
472// public IPredicateTraceListener geTraceListener() {
473// ensureInitialized();
474// return traceListener;
475// }
476
477 /**
478 * @param disc
479 * the new Disconnectable adapter.
480 */
481 public void addDisconnectable(Disconnectable disc) {
482 ensureInitialized();
483 disconnectables.add(disc);
484 }
485
486 /**
487 * @return the parallelExecutionEnabled
488 */
489 public boolean isParallelExecutionEnabled() {
490 return parallelExecutionEnabled;
491 }
492
493
494 public Logger getLogger() {
495 ensureInitialized();
496 return logger;
497 }
498
499 public IQueryRuntimeContext getRuntimeContext() {
500 ensureInitialized();
501 return runtimeContext;
502 }
503
504 public ReteRecipeCompiler getCompiler() {
505 ensureInitialized();
506 return compiler;
507 }
508
509 // /**
510 // * For internal use only: logs exceptions occurring during term evaluation inside the RETE net.
511 // * @param e
512 // */
513 // public void logEvaluatorException(Throwable e) {
514 // try {
515 // caughtExceptions.put(e);
516 // } catch (InterruptedException e1) {
517 // logEvaluatorException(e);
518 // }
519 // }
520 // /**
521 // * Polls the exceptions caught and logged during term evaluation by this RETE engine.
522 // * Recommended usage: iterate polling until null is returned.
523 // *
524 // * @return the next caught exception, or null if there are no more.
525 // */
526 // public Throwable getNextLoggedEvaluatorException() {
527 // return caughtExceptions.poll();
528 // }
529
530 void ensureInitialized() {
531 if (disposedOrUninitialized)
532 throw new IllegalStateException("Trying to use a Rete engine that has been disposed or has not yet been initialized.");
533
534 }
535
536 @Override
537 public IQueryResultProvider getResultProvider(PQuery query) {
538 return accessMatcher(query);
539 }
540
541 /**
542 * @since 1.4
543 */
544 @Override
545 public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) {
546 hintConfigurator.storeHint(query, hints);
547 return accessMatcher(query);
548 }
549
550 @Override
551 public IQueryResultProvider peekExistingResultProvider(PQuery query) {
552 ensureInitialized();
553 return matchers.get(query);
554 }
555
556 @Override
557 public void dispose() {
558 killEngine();
559 }
560
561 @Override
562 public boolean isCaching() {
563 return true;
564 }
565
566 /**
567 * @since 1.5
568 * @noreference Internal API, subject to change
569 */
570 public IQueryBackendHintProvider getHintConfiguration() {
571 return hintConfigurator;
572 }
573
574 @Override
575 public IQueryBackendFactory getFactory() {
576 return ReteBackendFactory.INSTANCE;
577 }
578
579}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java
new file mode 100644
index 00000000..38fe7c2f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java
@@ -0,0 +1,463 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10
11package tools.refinery.viatra.runtime.rete.matcher;
12
13import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
14import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
15import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
16import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
17import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
21import tools.refinery.viatra.runtime.matchers.util.Accuracy;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.rete.index.Indexer;
24import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
25import tools.refinery.viatra.runtime.rete.network.Node;
26import tools.refinery.viatra.runtime.rete.network.ProductionNode;
27import tools.refinery.viatra.runtime.rete.network.Receiver;
28import tools.refinery.viatra.runtime.rete.remote.Address;
29import tools.refinery.viatra.runtime.rete.single.CallbackNode;
30import tools.refinery.viatra.runtime.rete.single.TransformerNode;
31import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
32
33import java.util.Collection;
34import java.util.List;
35import java.util.Map;
36import java.util.Optional;
37import java.util.stream.Collectors;
38import java.util.stream.Stream;
39
40/**
41 * @author Gabor Bergmann
42 *
43 */
44public class RetePatternMatcher extends TransformerNode implements IQueryResultProvider {
45
46 protected ReteEngine engine;
47 protected IQueryRuntimeContext context;
48 protected ProductionNode productionNode;
49 protected RecipeTraceInfo productionNodeTrace;
50 protected Map<String, Integer> posMapping;
51 protected Map<Object, Receiver> taggedChildren = CollectionsFactory.createMap();
52 protected boolean connected = false; // is rete-wise connected to the
53 // production node?
54
55 /**
56 * @param productionNode
57 * a production node that matches this pattern without any parameter bindings
58 * @pre: Production must be local to the head container
59 */
60 public RetePatternMatcher(ReteEngine engine, RecipeTraceInfo productionNodeTrace) {
61 super(engine.getReteNet().getHeadContainer());
62 this.engine = engine;
63 this.context = engine.getRuntimeContext();
64 this.productionNodeTrace = productionNodeTrace;
65 final Address<? extends Node> productionAddress = reteContainer.getProvisioner()
66 .getOrCreateNodeByRecipe(productionNodeTrace);
67 if (!reteContainer.isLocal(productionAddress))
68 throw new IllegalArgumentException("@pre: Production must be local to the head container");
69 this.productionNode = (ProductionNode) reteContainer.resolveLocal(productionAddress);
70 this.posMapping = this.productionNode.getPosMapping();
71 this.reteContainer.getCommunicationTracker().registerDependency(this.productionNode, this);
72 }
73
74 /**
75 * @since 1.6
76 */
77 public ProductionNode getProductionNode() {
78 return productionNode;
79 }
80
81 public Tuple matchOneRandomly(Object[] inputMapping, boolean[] fixed) {
82 List<Tuple> allMatches = matchAll(inputMapping, fixed).collect(Collectors.toList());
83 if (allMatches == null || allMatches.isEmpty())
84 return null;
85 else
86 return allMatches.get((int) (Math.random() * allMatches.size()));
87 }
88
89 /**
90 * @since 2.0
91 */
92 public Stream<Tuple> matchAll(Object[] inputMapping, boolean[] fixed) {
93 // retrieving the projection
94 TupleMask mask = TupleMask.fromKeepIndicators(fixed);
95 Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping));
96
97 return matchAll(mask, inputSignature);
98
99 }
100
101 /**
102 * @since 2.0
103 */
104 public Stream<Tuple> matchAll(TupleMask mask, ITuple inputSignature) {
105 AllMatchFetcher fetcher = new AllMatchFetcher(engine.accessProjection(productionNodeTrace, mask),
106 context.wrapTuple(inputSignature.toImmutable()));
107 engine.reteNet.waitForReteTermination(fetcher);
108 return fetcher.getMatches();
109
110 }
111
112 /**
113 * @since 2.0
114 */
115 public Optional<Tuple> matchOne(Object[] inputMapping, boolean[] fixed) {
116 // retrieving the projection
117 TupleMask mask = TupleMask.fromKeepIndicators(fixed);
118 Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping));
119
120 return matchOne(mask, inputSignature);
121 }
122
123 /**
124 * @since 2.0
125 */
126 public Optional<Tuple> matchOne(TupleMask mask, ITuple inputSignature) {
127 SingleMatchFetcher fetcher = new SingleMatchFetcher(engine.accessProjection(productionNodeTrace, mask),
128 context.wrapTuple(inputSignature.toImmutable()));
129 engine.reteNet.waitForReteTermination(fetcher);
130 return Optional.ofNullable(fetcher.getMatch());
131 }
132
133 /**
134 * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true.
135 *
136 * @return the number of occurrences
137 */
138 public int count(Object[] inputMapping, boolean[] fixed) {
139 TupleMask mask = TupleMask.fromKeepIndicators(fixed);
140 Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping));
141
142 return count(mask, inputSignature);
143 }
144
145 /**
146 * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true.
147 *
148 * @return the number of occurrences
149 * @since 1.7
150 */
151 public int count(TupleMask mask, ITuple inputSignature) {
152 CountFetcher fetcher = new CountFetcher(engine.accessProjection(productionNodeTrace, mask),
153 context.wrapTuple(inputSignature.toImmutable()));
154 engine.reteNet.waitForReteTermination(fetcher);
155
156 return fetcher.getCount();
157 }
158
159 /**
160 * Counts the number of distinct tuples attainable from the match set by projecting match tuples according to the given mask.
161 *
162 *
163 * @return the size of the projection
164 * @since 2.1
165 */
166 public int projectionSize(TupleMask groupMask) {
167 ProjectionSizeFetcher fetcher = new ProjectionSizeFetcher(
168 (IterableIndexer) engine.accessProjection(productionNodeTrace, groupMask));
169 engine.reteNet.waitForReteTermination(fetcher);
170
171 return fetcher.getSize();
172 }
173
174 /**
175 * Connects a new external receiver that will receive update notifications from now on. The receiver will
176 * practically connect to the production node, the added value is unwrapping the updates for external use.
177 *
178 * @param synchronize
179 * if true, the contents of the production node will be inserted into the receiver after the connection
180 * is established.
181 */
182 public synchronized void connect(Receiver receiver, boolean synchronize) {
183 if (!connected) { // connect to the production node as a RETE-child
184 reteContainer.connect(productionNode, this);
185 connected = true;
186 }
187 if (synchronize)
188 reteContainer.connectAndSynchronize(this, receiver);
189 else
190 reteContainer.connect(this, receiver);
191 }
192
193 /**
194 * Connects a new external receiver that will receive update notifications from now on. The receiver will
195 * practically connect to the production node, the added value is unwrapping the updates for external use.
196 *
197 * The external receiver will be disconnectable later based on its tag.
198 *
199 * @param tag
200 * an identifier to recognize the child node by.
201 *
202 * @param synchronize
203 * if true, the contents of the production node will be inserted into the receiver after the connection
204 * is established.
205 *
206 */
207 public synchronized void connect(Receiver receiver, Object tag, boolean synchronize) {
208 taggedChildren.put(tag, receiver);
209 connect(receiver, synchronize);
210 }
211
212 /**
213 * Disconnects a child node.
214 */
215 public synchronized void disconnect(Receiver receiver) {
216 reteContainer.disconnect(this, receiver);
217 }
218
219 /**
220 * Disconnects the child node that was connected by specifying the given tag.
221 *
222 * @return if a child node was found registered with this tag.
223 */
224 public synchronized boolean disconnectByTag(Object tag) {
225 final Receiver receiver = taggedChildren.remove(tag);
226 final boolean found = receiver != null;
227 if (found)
228 disconnect(receiver);
229 return found;
230 }
231
232 @Override
233 protected Tuple transform(Tuple input) {
234 return context.unwrapTuple(input);
235 }
236
237 abstract class AbstractMatchFetcher implements Runnable {
238 Indexer indexer;
239 Tuple signature;
240
241 public AbstractMatchFetcher(Indexer indexer, Tuple signature) {
242 super();
243 this.indexer = indexer;
244 this.signature = signature;
245 }
246
247 @Override
248 public void run() {
249 fetch(indexer.get(signature));
250 }
251
252 protected abstract void fetch(Collection<Tuple> matches);
253
254 }
255
256 class AllMatchFetcher extends AbstractMatchFetcher {
257
258 public AllMatchFetcher(Indexer indexer, Tuple signature) {
259 super(indexer, signature);
260 }
261
262 Stream<Tuple> matches = null;
263
264 public Stream<Tuple> getMatches() {
265 return matches;
266 }
267
268 @Override
269 protected void fetch(Collection<Tuple> matches) {
270 if (matches == null)
271 this.matches = Stream.of();
272 else {
273 this.matches = matches.stream().map(context::unwrapTuple);
274 }
275
276 }
277
278 }
279
280 class SingleMatchFetcher extends AbstractMatchFetcher {
281
282 public SingleMatchFetcher(Indexer indexer, Tuple signature) {
283 super(indexer, signature);
284 }
285
286 Tuple match = null;
287
288 public Tuple getMatch() {
289 return match;
290 }
291
292 @Override
293 protected void fetch(Collection<Tuple> matches) {
294 if (matches != null && !matches.isEmpty())
295 match = context.unwrapTuple(matches.iterator().next());
296 }
297
298 // public void run() {
299 // Collection<Tuple> unscopedMatches = indexer.get(signature);
300 //
301 // // checking scopes
302 // if (unscopedMatches != null) {
303 // for (Tuple um : /* productionNode */unscopedMatches) {
304 // match = inputConnector.unwrapTuple(um);
305 // return;
306 //
307 // // Tuple ps = inputConnector.unwrapTuple(um);
308 // // boolean ok = true;
309 // // if (!ignoreScope) for (int k = 0; (k < ps.getSize()) && ok; k++) {
310 // // if (pcs[k].getParameterMode() == ParameterMode.INPUT) {
311 // // // ok = ok && (inputMapping[k]==ps.elements[k]);
312 // // // should now be true
313 // // } else // ParameterMode.OUTPUT
314 // // {
315 // // IEntity scopeParent = (IEntity) pcs[k].getParameterScope().getParent();
316 // // Integer containmentMode = pcs[k].getParameterScope().getContainmentMode();
317 // // if (containmentMode == Scope.BELOW)
318 // // ok = ok && ((IModelElement) ps.get(k)).isBelowNamespace(scopeParent);
319 // // else
320 // // /* case Scope.IN: */
321 // // ok = ok && scopeParent.equals(((IModelElement) ps.get(k)).getNamespace());
322 // // // note: getNamespace returns null instead of the
323 // // // (imaginary) modelspace root entity for top level
324 // // // elements;
325 // // // this is not a problem here as Scope.IN implies
326 // // // scopeParent != root.
327 // //
328 // // }
329 // // }
330 // //
331 // // if (ok) {
332 // // reteMatching = new ReteMatching(ps, posMapping);
333 // // return;
334 // // }
335 // }
336 // }
337 //
338 // }
339
340 }
341
342 class CountFetcher extends AbstractMatchFetcher {
343
344 public CountFetcher(Indexer indexer, Tuple signature) {
345 super(indexer, signature);
346 }
347
348 int count = 0;
349
350 public int getCount() {
351 return count;
352 }
353
354 @Override
355 protected void fetch(Collection<Tuple> matches) {
356 count = matches == null ? 0 : matches.size();
357 }
358
359 }
360
361 class ProjectionSizeFetcher implements Runnable {
362 IterableIndexer indexer;
363 int size = 0;
364
365 public ProjectionSizeFetcher(IterableIndexer indexer) {
366 super();
367 this.indexer = indexer;
368 }
369
370 @Override
371 public void run() {
372 size = indexer.getBucketCount();
373 }
374
375 public int getSize() {
376 return size;
377 }
378
379 }
380
381 private boolean[] notNull(Object[] parameters) {
382 boolean[] notNull = new boolean[parameters.length];
383 for (int i = 0; i < parameters.length; ++i)
384 notNull[i] = parameters[i] != null;
385 return notNull;
386 }
387
388
389
390 @Override
391 public boolean hasMatch(Object[] parameters) {
392 return countMatches(parameters) > 0;
393 }
394
395 @Override
396 public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) {
397 return count(parameterSeedMask, parameters) > 0;
398 }
399
400 @Override
401 public int countMatches(Object[] parameters) {
402 return count(parameters, notNull(parameters));
403 }
404
405 @Override
406 public int countMatches(TupleMask parameterSeedMask, ITuple parameters) {
407 return count(parameterSeedMask, parameters);
408 }
409
410
411 @Override
412 public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) {
413 return Optional.of((long)projectionSize(groupMask)); // always accurate
414 }
415
416 @Override
417 public Optional<Tuple> getOneArbitraryMatch(Object[] parameters) {
418 return matchOne(parameters, notNull(parameters));
419 }
420
421 @Override
422 public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) {
423 return matchOne(parameterSeedMask, parameters);
424 }
425
426 @Override
427 public Stream<Tuple> getAllMatches(Object[] parameters) {
428 return matchAll(parameters, notNull(parameters));
429 }
430
431 @Override
432 public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters) {
433 return matchAll(parameterSeedMask, parameters);
434 }
435
436 @Override
437 public IQueryBackend getQueryBackend() {
438 return engine;
439 }
440
441 @Override
442 public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow) {
443 // As a listener is added as a delayed command, they should be executed to make sure everything is consistent on
444 // return, see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=562369
445 engine.constructionWrapper(() -> {
446 final CallbackNode callbackNode = new CallbackNode(this.reteContainer, listener);
447 connect(callbackNode, listenerTag, fireNow);
448 return null;
449 });
450 }
451
452 @Override
453 public void removeUpdateListener(Object listenerTag) {
454 engine.constructionWrapper(() -> {
455 disconnectByTag(listenerTag);
456 return null;
457 });
458 }
459
460 public Indexer getInternalIndexer(TupleMask mask) {
461 return engine.accessProjection(productionNodeTrace, mask);
462 }
463}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java
new file mode 100644
index 00000000..876ddc99
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11/**
12 * Configuration of timely evaluation.
13 *
14 * @author Tamas Szabo
15 * @since 2.4
16 */
17public class TimelyConfiguration {
18
19 private final AggregatorArchitecture aggregatorArchitecture;
20 private final TimelineRepresentation timelineRepresentation;
21
22 public TimelyConfiguration(final TimelineRepresentation timelineRepresentation,
23 final AggregatorArchitecture aggregatorArchitecture) {
24 this.aggregatorArchitecture = aggregatorArchitecture;
25 this.timelineRepresentation = timelineRepresentation;
26 }
27
28 public AggregatorArchitecture getAggregatorArchitecture() {
29 return aggregatorArchitecture;
30 }
31
32 public TimelineRepresentation getTimelineRepresentation() {
33 return timelineRepresentation;
34 }
35
36 public enum AggregatorArchitecture {
37 /**
38 * Aggregands are copied over from lower timestamps to higher timestamps.
39 */
40 PARALLEL,
41
42 /**
43 * Aggregands are only present at the timestamp where they are inserted at.
44 * Only aggregate results are pushed towards higher timestamps during folding.
45 */
46 SEQUENTIAL
47 }
48
49 public enum TimelineRepresentation {
50 /**
51 * Only the first moment (timestamp) of appearance is maintained per tuple.
52 */
53 FIRST_ONLY,
54
55 /**
56 * Complete timeline (series of appearance & disappearance) is maintained per tuple.
57 */
58 FAITHFUL
59 }
60
61}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java
new file mode 100644
index 00000000..2777f169
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.matcher;
10
11import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
12import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
13import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture;
14import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
15
16/**
17 * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use non-scattered timely
18 * evaluation.
19 *
20 * @author Tamas Szabo
21 * @since 2.4
22 */
23public class TimelyReteBackendFactory extends ReteBackendFactory {
24
25 private final TimelyConfiguration configuration;
26
27 public static final TimelyReteBackendFactory FIRST_ONLY_SEQUENTIAL = new TimelyReteBackendFactory(
28 new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.SEQUENTIAL));
29 public static final TimelyReteBackendFactory FIRST_ONLY_PARALLEL = new TimelyReteBackendFactory(
30 new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.PARALLEL));
31 public static final TimelyReteBackendFactory FAITHFUL_SEQUENTIAL = new TimelyReteBackendFactory(
32 new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.SEQUENTIAL));
33 public static final TimelyReteBackendFactory FAITHFUL_PARALLEL = new TimelyReteBackendFactory(
34 new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.PARALLEL));
35
36 public TimelyReteBackendFactory(final TimelyConfiguration configuration) {
37 this.configuration = configuration;
38 }
39
40 @Override
41 public IQueryBackend create(final IQueryBackendContext context) {
42 return create(context, false, configuration);
43 }
44
45 @Override
46 public int hashCode() {
47 return TimelyReteBackendFactory.class.hashCode();
48 }
49
50 @Override
51 public boolean equals(final Object obj) {
52 if (this == obj) {
53 return true;
54 }
55 if (obj == null) {
56 return false;
57 }
58 if (!(obj instanceof TimelyReteBackendFactory)) {
59 return false;
60 }
61 return true;
62 }
63
64}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java
new file mode 100644
index 00000000..4aef0f96
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.misc;
11
12import java.util.Collection;
13import java.util.LinkedList;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.rete.network.ReteContainer;
18import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
19
20/**
21 * @author Gabor Bergmann
22 *
23 * A bag is a container that tuples can be dumped into. Does NOT propagate updates! Optimized for small contents
24 * size OR positive updates only.
25 */
26public class Bag extends SimpleReceiver {
27
28 public Collection<Tuple> contents;
29
30 public Bag(ReteContainer reteContainer) {
31 super(reteContainer);
32 contents = new LinkedList<Tuple>();
33 }
34
35 @Override
36 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
37 if (direction == Direction.INSERT)
38 contents.add(updateElement);
39 else
40 contents.remove(updateElement);
41 }
42
43}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java
new file mode 100644
index 00000000..980d3eee
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.misc;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.StandardNode;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22/**
23 * Node that always contains a single constant Tuple
24 *
25 * @author Gabor Bergmann
26 */
27public class ConstantNode extends StandardNode {
28
29 protected Tuple constant;
30
31 /**
32 * @param constant
33 * will be wrapped using {@link IQueryRuntimeContext#wrapTuple(Tuple)}
34 */
35 public ConstantNode(ReteContainer reteContainer, Tuple constant) {
36 super(reteContainer);
37 this.constant = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapTuple(constant);
38 }
39
40 @Override
41 public void pullInto(Collection<Tuple> collector, boolean flush) {
42 collector.add(constant);
43 }
44
45 @Override
46 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
47 collector.put(constant, Timestamp.INSERT_AT_ZERO_TIMELINE);
48 }
49
50}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java
new file mode 100644
index 00000000..efba3117
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.misc;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.rete.network.Network;
14import tools.refinery.viatra.runtime.rete.network.ReteContainer;
15
16/**
17 * Default configuration for DeltaMonitor.
18 *
19 * @author Gabor Bergmann
20 *
21 */
22public class DefaultDeltaMonitor extends DeltaMonitor<Tuple> {
23
24 /**
25 * @param reteContainer
26 */
27 public DefaultDeltaMonitor(ReteContainer reteContainer) {
28 super(reteContainer);
29 }
30
31 /**
32 * @param network
33 */
34 public DefaultDeltaMonitor(Network network) {
35 super(network.getHeadContainer());
36 }
37
38 @Override
39 public Tuple statelessConvert(Tuple tuple) {
40 return tuple;
41 }
42
43}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java
new file mode 100644
index 00000000..82b6ecda
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java
@@ -0,0 +1,111 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.misc;
11
12import java.util.Collection;
13import java.util.LinkedHashSet;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Clearable;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20
21/**
22 * A monitoring object that connects to the rete network as a receiver to reflect changes since an arbitrary state
23 * acknowledged by the client. Match tuples are represented by a type MatchType.
24 *
25 * <p>
26 * <b>Usage</b>. If a new matching is found, it appears in the matchFoundEvents collection, and disappears when that
27 * particular matching cannot be found anymore. If the event of finding a match has been processed by the client, it can
28 * be removed manually. In this case, when a previously found matching is lost, the Tuple will appear in the
29 * matchLostEvents collection, and disappear upon finding the same matching again. "Matching lost" events can also be
30 * acknowledged by removing a Tuple from the collection. If the matching is found once again, it will return to
31 * matchFoundEvents.
32 *
33 * <p>
34 * <b>Technical notes</b>. Does NOT propagate updates!
35 *
36 * By overriding statelessConvert(), results can be stored to a MatchType. MatchType must provide equals() and
37 * hashCode() reflecting its contents. The default implementation (DefaultDeltaMonitor) uses Tuple as MatchType.
38 *
39 * By overriding statelessFilter(), some tuples can be filtered.
40 *
41 * @author Gabor Bergmann
42 *
43 */
44public abstract class DeltaMonitor<MatchType> extends SimpleReceiver implements Clearable {
45
46 /**
47 * matches that are newly found
48 */
49 public Collection<MatchType> matchFoundEvents;
50 /**
51 * matches that are newly lost
52 */
53 public Collection<MatchType> matchLostEvents;
54
55 /**
56 * @param reteContainer
57 */
58 public DeltaMonitor(ReteContainer reteContainer) {
59 super(reteContainer);
60 matchFoundEvents = new LinkedHashSet<MatchType>();
61 matchLostEvents = new LinkedHashSet<MatchType>();
62 reteContainer.registerClearable(this);
63 }
64
65 // /**
66 // * Build a delta monitor into the head container of the network.
67 // *
68 // * @param network
69 // */
70 // public DeltaMonitor(Network network) {
71 // this(network.getHeadContainer());
72 // }
73
74 /**
75 * Override this method to provide a lightweight, stateless filter on the tuples
76 *
77 * @param tuple
78 * the occurrence that is to be filtered
79 * @return true if this tuple should be monitored, false if ignored
80 */
81 public boolean statelessFilter(Tuple tuple) {
82 return true;
83 }
84
85 public abstract MatchType statelessConvert(Tuple tuple);
86
87 @Override
88 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
89 if (statelessFilter(updateElement)) {
90 MatchType match = statelessConvert(updateElement);
91 if (direction == Direction.INSERT) {
92 if (!matchLostEvents.remove(match)) // either had before but
93 // lost
94 matchFoundEvents.add(match); // or brand-new
95 } else // revoke
96 {
97 if (!matchFoundEvents.remove(match)) // either never found
98 // in the first
99 // place
100 matchLostEvents.add(match); // or newly lost
101 }
102 }
103 }
104
105 @Override
106 public void clear() {
107 matchFoundEvents.clear();
108 matchLostEvents.clear();
109 }
110
111}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java
new file mode 100644
index 00000000..dcf9ae78
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java
@@ -0,0 +1,109 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.misc;
10
11import java.util.Collection;
12import java.util.Collections;
13
14import tools.refinery.viatra.runtime.rete.network.BaseNode;
15import tools.refinery.viatra.runtime.rete.network.Receiver;
16import tools.refinery.viatra.runtime.rete.network.ReteContainer;
17import tools.refinery.viatra.runtime.rete.network.Supplier;
18import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
19import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
20import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
21import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
22
23/**
24 * @author Bergmann Gabor
25 *
26 */
27public abstract class SimpleReceiver extends BaseNode implements Receiver {
28
29 protected Supplier parent = null;
30 /**
31 * @since 1.6
32 */
33 protected final Mailbox mailbox;
34
35 /**
36 * @param reteContainer
37 */
38 public SimpleReceiver(ReteContainer reteContainer) {
39 super(reteContainer);
40 mailbox = instantiateMailbox();
41 reteContainer.registerClearable(mailbox);
42 }
43
44 /**
45 * Instantiates the {@link Mailbox} of this receiver.
46 * Subclasses may override this method to provide their own mailbox implementation.
47 *
48 * @return the mailbox
49 * @since 2.0
50 */
51 protected Mailbox instantiateMailbox() {
52 if (this.reteContainer.isTimelyEvaluation()) {
53 return new TimelyMailbox(this, this.reteContainer);
54 } else {
55 return new BehaviorChangingMailbox(this, this.reteContainer);
56 }
57 }
58
59 @Override
60 public Mailbox getMailbox() {
61 return this.mailbox;
62 }
63
64 @Override
65 public void appendParent(Supplier supplier) {
66 if (parent == null)
67 parent = supplier;
68 else
69 throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent
70 + ") and cannot connect to additional parent (" + supplier
71 + ") as it is not a Uniqueness Enforcer Node. ");
72 }
73
74 @Override
75 public void removeParent(Supplier supplier) {
76 if (parent == supplier)
77 parent = null;
78 else
79 throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not "
80 + supplier);
81 }
82
83 @Override
84 public Collection<Supplier> getParents() {
85 if (parent == null)
86 return Collections.emptySet();
87 else
88 return Collections.singleton(parent);
89 }
90
91 /**
92 * Disconnects this node from the network. Can be called publicly.
93 *
94 * @pre: child nodes, if any, must already be disconnected.
95 */
96 public void disconnectFromNetwork() {
97 if (parent != null)
98 reteContainer.disconnect(parent, this);
99 }
100
101 @Override
102 public void assignTraceInfo(TraceInfo traceInfo) {
103 super.assignTraceInfo(traceInfo);
104 if (traceInfo.propagateFromStandardNodeToSupplierParent())
105 if (parent != null)
106 parent.acceptPropagatedTraceInfo(traceInfo);
107 }
108
109} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java
new file mode 100644
index 00000000..2469d6bd
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java
@@ -0,0 +1,108 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network;
10
11import java.util.Collections;
12import java.util.HashSet;
13import java.util.Set;
14import java.util.TreeSet;
15
16import tools.refinery.viatra.runtime.rete.traceability.PatternTraceInfo;
17import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
18
19/**
20 * Base implementation for a Rete node.
21 *
22 * @author Bergmann Gabor
23 *
24 */
25public abstract class BaseNode implements Node {
26
27 protected ReteContainer reteContainer;
28 protected long nodeId;
29 protected Object tag;
30 protected Set<TraceInfo> traceInfos;
31
32 /**
33 * @param reteContainer
34 * the container to create this node in
35 */
36 public BaseNode(ReteContainer reteContainer) {
37 super();
38 this.reteContainer = reteContainer;
39 this.nodeId = reteContainer.registerNode(this);
40 this.traceInfos = new HashSet<TraceInfo>();
41 }
42
43 @Override
44 public String toString() {
45 if (tag != null)
46 return toStringCore() + "->" + getTraceInfoPatternsEnumerated() + "{" + tag.toString() + "}";
47 else
48 return toStringCore() + "->" + getTraceInfoPatternsEnumerated();
49 }
50
51 /**
52 * clients should override this to append before the tag / trace indicators
53 */
54 protected String toStringCore() {
55 return "[" + nodeId + "]" + getClass().getSimpleName();
56 }
57
58 @Override
59 public ReteContainer getContainer() {
60 return reteContainer;
61 }
62
63 @Override
64 public long getNodeId() {
65 return nodeId;
66 }
67
68 @Override
69 public Object getTag() {
70 return tag;
71 }
72
73 @Override
74 public void setTag(Object tag) {
75 this.tag = tag;
76 }
77
78 @Override
79 public Set<TraceInfo> getTraceInfos() {
80 return Collections.unmodifiableSet(traceInfos);
81 }
82
83 @Override
84 public void assignTraceInfo(TraceInfo traceInfo) {
85 traceInfos.add(traceInfo);
86 traceInfo.assignNode(this);
87 }
88
89 @Override
90 public void acceptPropagatedTraceInfo(TraceInfo traceInfo) {
91 assignTraceInfo(traceInfo);
92 }
93
94 /**
95 * Descendants should use this in e.g. logging
96 */
97 protected String getTraceInfoPatternsEnumerated() {
98 TreeSet<String> patternNames = new TreeSet<String>();
99 for (TraceInfo trInfo : traceInfos) {
100 if (trInfo instanceof PatternTraceInfo) {
101 final String pName = ((PatternTraceInfo) trInfo).getPatternName();
102 patternNames.add(pName);
103 }
104 }
105 return patternNames.toString();
106 }
107
108} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java
new file mode 100644
index 00000000..b261d19d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.rete.network;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.rete.aggregation.IndexerBasedAggregatorNode;
14import tools.refinery.viatra.runtime.rete.boundary.InputConnector;
15import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode;
16import tools.refinery.viatra.runtime.rete.index.DualInputNode;
17import tools.refinery.viatra.runtime.rete.index.Indexer;
18import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
19import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
20import tools.refinery.viatra.runtime.rete.recipes.*;
21import tools.refinery.viatra.runtime.rete.remote.Address;
22import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
23
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.List;
27
28/**
29 * Class responsible for connecting freshly instantiating Rete nodes to their parents.
30 *
31 * @author Bergmann Gabor
32 *
33 */
34class ConnectionFactory {
35 ReteContainer reteContainer;
36
37 public ConnectionFactory(ReteContainer reteContainer) {
38 super();
39 this.reteContainer = reteContainer;
40 }
41
42 // TODO move to node implementation instead?
43 private boolean isStateful(ReteNodeRecipe recipe) {
44 return recipe instanceof ProjectionIndexerRecipe || recipe instanceof IndexerBasedAggregatorRecipe
45 || recipe instanceof SingleColumnAggregatorRecipe || recipe instanceof ExpressionEnforcerRecipe
46 || recipe instanceof TransitiveClosureRecipe || recipe instanceof ProductionRecipe
47 || recipe instanceof UniquenessEnforcerRecipe || recipe instanceof RelationEvaluationRecipe;
48
49 }
50
51 /**
52 * PRE: nodes for parent recipes must already be created and registered
53 * <p>
54 * PRE: must not be an input node (for which {@link InputConnector} is responsible)
55 */
56 public void connectToParents(RecipeTraceInfo recipeTrace, Node freshNode) {
57 final ReteNodeRecipe recipe = recipeTrace.getRecipe();
58 if (recipe instanceof ConstantRecipe) {
59 // NO-OP
60 } else if (recipe instanceof InputRecipe) {
61 throw new IllegalArgumentException(
62 ConnectionFactory.class.getSimpleName() + " not intended for input connection: " + recipe);
63 } else if (recipe instanceof SingleParentNodeRecipe) {
64 final Receiver receiver = (Receiver) freshNode;
65 ReteNodeRecipe parentRecipe = ((SingleParentNodeRecipe) recipe).getParent();
66 connectToParent(recipe, receiver, parentRecipe);
67 } else if (recipe instanceof RelationEvaluationRecipe) {
68 List<ReteNodeRecipe> parentRecipes = ((MultiParentNodeRecipe) recipe).getParents();
69 List<Supplier> parentSuppliers = new ArrayList<Supplier>();
70 for (final ReteNodeRecipe parentRecipe : parentRecipes) {
71 parentSuppliers.add(getSupplierForRecipe(parentRecipe));
72 }
73 ((RelationEvaluatorNode) freshNode).connectToParents(parentSuppliers);
74 } else if (recipe instanceof BetaRecipe) {
75 final DualInputNode beta = (DualInputNode) freshNode;
76 final ArrayList<RecipeTraceInfo> parentTraces = new ArrayList<RecipeTraceInfo>(
77 recipeTrace.getParentRecipeTraces());
78 Slots slots = avoidActiveNodeConflict(parentTraces.get(0), parentTraces.get(1));
79 beta.connectToIndexers(slots.primary, slots.secondary);
80 } else if (recipe instanceof IndexerBasedAggregatorRecipe) {
81 final IndexerBasedAggregatorNode aggregator = (IndexerBasedAggregatorNode) freshNode;
82 final IndexerBasedAggregatorRecipe aggregatorRecipe = (IndexerBasedAggregatorRecipe) recipe;
83 aggregator.initializeWith((ProjectionIndexer) resolveIndexer(aggregatorRecipe.getParent()));
84 } else if (recipe instanceof MultiParentNodeRecipe) {
85 final Receiver receiver = (Receiver) freshNode;
86 List<ReteNodeRecipe> parentRecipes = ((MultiParentNodeRecipe) recipe).getParents();
87 for (ReteNodeRecipe parentRecipe : parentRecipes) {
88 connectToParent(recipe, receiver, parentRecipe);
89 }
90 }
91 }
92
93 private Indexer resolveIndexer(final IndexerRecipe indexerRecipe) {
94 final Address<? extends Node> address = reteContainer.getNetwork().getExistingNodeByRecipe(indexerRecipe);
95 return (Indexer) reteContainer.resolveLocal(address);
96 }
97
98 private void connectToParent(ReteNodeRecipe recipe, Receiver freshNode, ReteNodeRecipe parentRecipe) {
99 final Supplier parentSupplier = getSupplierForRecipe(parentRecipe);
100
101 // special synch
102 if (freshNode instanceof ReinitializedNode) {
103 Collection<Tuple> tuples = new ArrayList<Tuple>();
104 parentSupplier.pullInto(tuples, true);
105 ((ReinitializedNode) freshNode).reinitializeWith(tuples);
106 reteContainer.connect(parentSupplier, freshNode);
107 } else { // default case
108 // stateless nodes do not have to be synced with contents UNLESS they already have children (recursive
109 // corner case)
110 if (isStateful(recipe)
111 || ((freshNode instanceof Supplier) && !((Supplier) freshNode).getReceivers().isEmpty())) {
112 reteContainer.connectAndSynchronize(parentSupplier, freshNode);
113 } else {
114 // stateless node, no synch
115 reteContainer.connect(parentSupplier, freshNode);
116 }
117 }
118 }
119
120 private Supplier getSupplierForRecipe(ReteNodeRecipe recipe) {
121 @SuppressWarnings("unchecked")
122 final Address<? extends Supplier> parentAddress = (Address<? extends Supplier>) reteContainer.getNetwork()
123 .getExistingNodeByRecipe(recipe);
124 final Supplier supplier = reteContainer.getProvisioner().asSupplier(parentAddress);
125 return supplier;
126 }
127
128 /**
129 * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of
130 * the two indexers is supported.
131 *
132 * @return a replacement for the secondary Indexers, if needed
133 */
134 private Slots avoidActiveNodeConflict(final RecipeTraceInfo primarySlot, final RecipeTraceInfo secondarySlot) {
135 Slots result = new Slots() {
136 {
137 primary = (IterableIndexer) resolveIndexer((ProjectionIndexerRecipe) primarySlot.getRecipe());
138 secondary = resolveIndexer((IndexerRecipe) secondarySlot.getRecipe());
139 }
140 };
141 if (activeNodeConflict(result.primary, result.secondary))
142 if (result.secondary instanceof IterableIndexer)
143 result.secondary = resolveActiveIndexer(secondarySlot);
144 else
145 result.primary = (IterableIndexer) resolveActiveIndexer(primarySlot);
146 return result;
147 }
148
149 private Indexer resolveActiveIndexer(final RecipeTraceInfo inactiveIndexerTrace) {
150 final RecipeTraceInfo activeIndexerTrace = reteContainer.getProvisioner()
151 .accessActiveIndexer(inactiveIndexerTrace);
152 reteContainer.getProvisioner().getOrCreateNodeByRecipe(activeIndexerTrace);
153 return resolveIndexer((ProjectionIndexerRecipe) activeIndexerTrace.getRecipe());
154 }
155
156 private static class Slots {
157 IterableIndexer primary;
158 Indexer secondary;
159 }
160
161 /**
162 * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of
163 * the two indexers is supported.
164 *
165 * @return true if there is a conflict of active nodes.
166 */
167 private boolean activeNodeConflict(Indexer primarySlot, Indexer secondarySlot) {
168 return !primarySlot.equals(secondarySlot) && primarySlot.getActiveNode().equals(secondarySlot.getActiveNode());
169 }
170
171}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java
new file mode 100644
index 00000000..c22b06d8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network;
10
11import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public interface IGroupable {
18
19 /**
20 * @return the current group of the mailbox
21 * @since 1.7
22 */
23 CommunicationGroup getCurrentGroup();
24
25 /**
26 * Sets the current group of the mailbox
27 * @since 1.7
28 */
29 void setCurrentGroup(CommunicationGroup group);
30
31} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java
new file mode 100644
index 00000000..64f59ff3
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java
@@ -0,0 +1,408 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.List;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Set;
19import java.util.concurrent.locks.Lock;
20import java.util.concurrent.locks.ReadWriteLock;
21import java.util.concurrent.locks.ReentrantReadWriteLock;
22
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
25import tools.refinery.viatra.runtime.matchers.util.Direction;
26import tools.refinery.viatra.runtime.rete.boundary.InputConnector;
27import tools.refinery.viatra.runtime.rete.matcher.ReteEngine;
28import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
29import tools.refinery.viatra.runtime.rete.remote.Address;
30import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
31import tools.refinery.viatra.runtime.rete.util.Options;
32
33/**
34 * @author Gabor Bergmann
35 *
36 */
37public class Network {
38 final int threads;
39
40 protected ArrayList<ReteContainer> containers;
41 ReteContainer headContainer;
42 private int firstContainer = 0;
43 private int nextContainer = 0;
44
45 // the following fields exist only if threads > 0
46 protected Map<ReteContainer, Long> globalTerminationCriteria = null;
47 protected Map<ReteContainer, Long> reportedClocks = null;
48 protected Lock updateLock = null; // grab during normal update operations
49 protected Lock structuralChangeLock = null; // grab if the network structure
50 // is to
51 // be changed
52
53 // Knowledge of the outside world
54 private ReteEngine engine;
55 protected NodeFactory nodeFactory;
56 protected InputConnector inputConnector;
57
58 // Node and recipe administration
59 // incl. addresses for existing nodes by recipe (where available)
60 // Maintained by NodeProvisioner of each container
61 Map<ReteNodeRecipe, Address<? extends Node>> nodesByRecipe = CollectionsFactory.createMap();
62 Set<RecipeTraceInfo> recipeTraces = CollectionsFactory.createSet();
63
64 /**
65 * @throws IllegalStateException
66 * if no node has been constructed for the recipe
67 */
68 public synchronized Address<? extends Node> getExistingNodeByRecipe(ReteNodeRecipe recipe) {
69 final Address<? extends Node> node = nodesByRecipe.get(recipe);
70 if (node == null)
71 throw new IllegalStateException(String.format("Rete node for recipe %s not constructed yet.", recipe));
72 return node;
73 }
74
75 /**
76 * @return null if no node has been constructed for the recipe
77 */
78 public synchronized Address<? extends Node> getNodeByRecipeIfExists(ReteNodeRecipe recipe) {
79 final Address<? extends Node> node = nodesByRecipe.get(recipe);
80 return node;
81 }
82
83 /**
84 * @param threads
85 * the number of threads to operate the network with; 0 means single-threaded operation, 1 starts an
86 * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers.
87 */
88 public Network(int threads, ReteEngine engine) {
89 super();
90 this.threads = threads;
91 this.engine = engine;
92 this.inputConnector = new InputConnector(this);
93 this.nodeFactory = new NodeFactory(engine.getLogger());
94
95 containers = new ArrayList<ReteContainer>();
96 firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD
97 nextContainer = firstContainer;
98
99 if (threads > 0) {
100 globalTerminationCriteria = CollectionsFactory.createMap();
101 reportedClocks = CollectionsFactory.createMap();
102 ReadWriteLock rwl = new ReentrantReadWriteLock();
103 updateLock = rwl.readLock();
104 structuralChangeLock = rwl.writeLock();
105 for (int i = 0; i < threads; ++i)
106 containers.add(new ReteContainer(this, true));
107 } else
108 containers.add(new ReteContainer(this, false));
109
110 headContainer = containers.get(0);
111 }
112
113 /**
114 * Kills this Network along with all containers and message consumption cycles.
115 */
116 public void kill() {
117 for (ReteContainer container : containers) {
118 container.kill();
119 }
120 containers.clear();
121 }
122
123 /**
124 * Returns the head container, that is guaranteed to reside in the same JVM as the Network object.
125 */
126 public ReteContainer getHeadContainer() {
127 return headContainer;
128 }
129
130 /**
131 * Returns the next container in round-robin fashion. Configurable not to yield head container.
132 */
133 public ReteContainer getNextContainer() {
134 if (nextContainer >= containers.size())
135 nextContainer = firstContainer;
136 return containers.get(nextContainer++);
137 }
138
139 /**
140 * Internal message delivery method.
141 *
142 * @pre threads > 0
143 */
144 private void sendUpdate(Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
145 ReteContainer affectedContainer = receiver.getContainer();
146 synchronized (globalTerminationCriteria) {
147 long newCriterion = affectedContainer.sendUpdateToLocalAddress(receiver, direction, updateElement);
148 terminationCriterion(affectedContainer, newCriterion);
149 }
150 }
151
152 /**
153 * Internal message delivery method for single-threaded operation
154 *
155 * @pre threads == 0
156 */
157 private void sendUpdateSingleThreaded(Address<? extends Receiver> receiver, Direction direction,
158 Tuple updateElement) {
159 ReteContainer affectedContainer = receiver.getContainer();
160 affectedContainer.sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement);
161 }
162
163 /**
164 * Internal message delivery method.
165 *
166 * @pre threads > 0
167 */
168 private void sendUpdates(Address<? extends Receiver> receiver, Direction direction,
169 Collection<Tuple> updateElements) {
170 if (updateElements.isEmpty())
171 return;
172 ReteContainer affectedContainer = receiver.getContainer();
173 synchronized (globalTerminationCriteria) {
174 long newCriterion = affectedContainer.sendUpdatesToLocalAddress(receiver, direction, updateElements);
175 terminationCriterion(affectedContainer, newCriterion);
176 }
177 }
178
179 /**
180 * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may
181 * reside in any of the containers associated with this network. To be called from a user thread during normal
182 * operation, NOT during construction.
183 *
184 * @since 2.4
185 */
186 public void sendExternalUpdate(Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
187 if (threads > 0) {
188 try {
189 updateLock.lock();
190 sendUpdate(receiver, direction, updateElement);
191 } finally {
192 updateLock.unlock();
193 }
194 } else {
195 sendUpdateSingleThreaded(receiver, direction, updateElement);
196 // getHeadContainer().
197 }
198 }
199
200 /**
201 * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may
202 * reside in any of the containers associated with this network. To be called from a user thread during
203 * construction.
204 *
205 * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may
206 * span through network calls, that's why it's not enforced here )
207 *
208 * @return the value of the target container's clock at the time when the message was accepted into its message
209 * queue
210 * @since 2.4
211 */
212 public void sendConstructionUpdate(Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
213 // structuralChangeLock.lock();
214 if (threads > 0)
215 sendUpdate(receiver, direction, updateElement);
216 else
217 receiver.getContainer().sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement);
218 // structuralChangeLock.unlock();
219 }
220
221 /**
222 * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial
223 * matching. The node may reside in any of the containers associated with this network. To be called from a user
224 * thread during construction.
225 *
226 * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may
227 * span through network calls, that's why it's not enforced here )
228 *
229 * @since 2.4
230 */
231 public void sendConstructionUpdates(Address<? extends Receiver> receiver, Direction direction,
232 Collection<Tuple> updateElements) {
233 // structuralChangeLock.lock();
234 if (threads > 0)
235 sendUpdates(receiver, direction, updateElements);
236 else
237 receiver.getContainer().sendUpdatesToLocalAddressSingleThreaded(receiver, direction, updateElements);
238 // structuralChangeLock.unlock();
239 }
240
241 /**
242 * Establishes connection between a supplier and a receiver node, regardless which container they are in. Not to be
243 * called remotely, because this method enforces the structural lock.
244 *
245 * @param supplier
246 * @param receiver
247 * @param synchronise
248 * indicates whether the receiver should be synchronised to the current contents of the supplier
249 */
250 public void connectRemoteNodes(Address<? extends Supplier> supplier, Address<? extends Receiver> receiver,
251 boolean synchronise) {
252 try {
253 if (threads > 0)
254 structuralChangeLock.lock();
255 receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise);
256 } finally {
257 if (threads > 0)
258 structuralChangeLock.unlock();
259 }
260 }
261
262 /**
263 * Severs connection between a supplier and a receiver node, regardless which container they are in. Not to be
264 * called remotely, because this method enforces the structural lock.
265 *
266 * @param supplier
267 * @param receiver
268 * @param desynchronise
269 * indicates whether the current contents of the supplier should be subtracted from the receiver
270 */
271 public void disconnectRemoteNodes(Address<? extends Supplier> supplier, Address<? extends Receiver> receiver,
272 boolean desynchronise) {
273 try {
274 if (threads > 0)
275 structuralChangeLock.lock();
276 receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise);
277 } finally {
278 if (threads > 0)
279 structuralChangeLock.unlock();
280 }
281 }
282
283 /**
284 * Containers use this method to report whenever they run out of messages in their queue.
285 *
286 * To be called from the thread of the reporting container.
287 *
288 * @pre threads > 0.
289 * @param reportingContainer
290 * the container reporting the emptiness of its message queue.
291 * @param clock
292 * the value of the container's clock when reporting.
293 * @param localTerminationCriteria
294 * the latest clock values this container has received from other containers since the last time it
295 * reported termination.
296 */
297 void reportLocalUpdateTermination(ReteContainer reportingContainer, long clock,
298 Map<ReteContainer, Long> localTerminationCriteria) {
299 synchronized (globalTerminationCriteria) {
300 for (Entry<ReteContainer, Long> criterion : localTerminationCriteria.entrySet()) {
301 terminationCriterion(criterion.getKey(), criterion.getValue());
302 }
303
304 reportedClocks.put(reportingContainer, clock);
305 Long criterion = globalTerminationCriteria.get(reportingContainer);
306 if (criterion != null && criterion < clock)
307 globalTerminationCriteria.remove(reportingContainer);
308
309 if (globalTerminationCriteria.isEmpty())
310 globalTerminationCriteria.notifyAll();
311 }
312 }
313
314 /**
315 * @pre threads > 0
316 */
317 private void terminationCriterion(ReteContainer affectedContainer, long newCriterion) {
318 synchronized (globalTerminationCriteria) {
319 Long oldCriterion = globalTerminationCriteria.get(affectedContainer);
320 Long oldClock = reportedClocks.get(affectedContainer);
321 long relevantClock = oldClock == null ? 0 : oldClock;
322 if ((relevantClock <= newCriterion) && (oldCriterion == null || oldCriterion < newCriterion)) {
323 globalTerminationCriteria.put(affectedContainer, newCriterion);
324 }
325 }
326 }
327
328 /**
329 * Waits until all rete update operations are settled in all containers. Returns immediately, if no updates are
330 * pending.
331 *
332 * To be called from any user thread.
333 */
334 public void waitForReteTermination() {
335 if (threads > 0) {
336 synchronized (globalTerminationCriteria) {
337 while (!globalTerminationCriteria.isEmpty()) {
338 try {
339 globalTerminationCriteria.wait();
340 } catch (InterruptedException e) {
341
342 }
343 }
344 }
345 } else
346 headContainer.deliverMessagesSingleThreaded();
347 }
348
349 /**
350 * Waits to execute action until all rete update operations are settled in all containers. Runs action and returns
351 * immediately, if no updates are pending. The given action is guaranteed to be run when the terminated state still
352 * persists.
353 *
354 * @param action
355 * the action to be run when reaching the steady-state.
356 *
357 * To be called from any user thread.
358 */
359 public void waitForReteTermination(Runnable action) {
360 if (threads > 0) {
361 synchronized (globalTerminationCriteria) {
362 while (!globalTerminationCriteria.isEmpty()) {
363 try {
364 globalTerminationCriteria.wait();
365 } catch (InterruptedException e) {
366
367 }
368 }
369 action.run();
370 }
371 } else {
372 headContainer.deliverMessagesSingleThreaded();
373 action.run();
374 }
375
376 }
377
378 /**
379 * @return an unmodifiable set of known recipe traces
380 */
381 public Set<RecipeTraceInfo> getRecipeTraces() {
382 return Collections.unmodifiableSet(recipeTraces);
383 }
384
385 /**
386 * @return an unmodifiable list of containers
387 */
388 public List<ReteContainer> getContainers() {
389 return Collections.unmodifiableList(containers);
390 }
391
392 public Lock getStructuralChangeLock() {
393 return structuralChangeLock;
394 }
395
396 public NodeFactory getNodeFactory() {
397 return nodeFactory;
398 }
399
400 public InputConnector getInputConnector() {
401 return inputConnector;
402 }
403
404 public ReteEngine getEngine() {
405 return engine;
406 }
407
408}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java
new file mode 100644
index 00000000..c6ba34c4
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network;
10
11import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
12
13/**
14 * {@link Node}s implementing this interface are sensitive to changes in the dependency graph maintained by the
15 * {@link CommunicationTracker}. The {@link CommunicationTracker} notifies these nodes whenever the SCC of this node is
16 * affected by changes to the dependency graph. Depending on whether this node is contained in a recursive group or not,
17 * it may behave differently, and the {@link NetworkStructureChangeSensitiveNode#networkStructureChanged()} method can
18 * be used to perform changes in behavior.
19 *
20 * @author Tamas Szabo
21 * @since 2.3
22 */
23public interface NetworkStructureChangeSensitiveNode extends Node {
24
25 /**
26 * At the time of the invocation, the dependency graph has already been updated.
27 */
28 public void networkStructureChanged();
29
30}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java
new file mode 100644
index 00000000..e8ab615a
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java
@@ -0,0 +1,62 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
15import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
16
17/**
18 * A node of a rete network, should be uniquely identified by network and nodeId. NodeId can be requested by registering
19 * at the Network on construction.
20 *
21 * @author Gabor Bergmann
22 */
23public interface Node {
24 /**
25 * @return the network this node belongs to.
26 */
27 ReteContainer getContainer();
28
29 /**
30 * @return the identifier unique to this node within the network.
31 */
32 long getNodeId();
33
34 /**
35 * Assigns a descriptive tag to the node
36 */
37 void setTag(Object tag);
38
39 /**
40 * @return the tag of the node
41 */
42 Object getTag();
43
44 /**
45 * @return unmodifiable view of the list of traceability infos assigned to this node
46 */
47 Set<TraceInfo> getTraceInfos();
48
49 /**
50 * assigns new traceability info to this node
51 */
52 void assignTraceInfo(TraceInfo traceInfo);
53 /**
54 * accepts traceability info propagated to this node
55 */
56 void acceptPropagatedTraceInfo(TraceInfo traceInfo);
57
58 default CommunicationTracker getCommunicationTracker() {
59 return getContainer().getCommunicationTracker();
60 }
61
62}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java
new file mode 100644
index 00000000..3e4ea4e0
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java
@@ -0,0 +1,376 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.rete.network;
11
12import org.apache.log4j.Logger;
13import org.eclipse.emf.common.util.EMap;
14import tools.refinery.viatra.runtime.rete.itc.alg.representative.RepresentativeElectionAlgorithm;
15import tools.refinery.viatra.runtime.rete.itc.alg.representative.StronglyConnectedComponentAlgorithm;
16import tools.refinery.viatra.runtime.rete.itc.alg.representative.WeaklyConnectedComponentAlgorithm;
17import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
18import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
19import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator;
20import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
21import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
22import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
23import tools.refinery.viatra.runtime.rete.aggregation.ColumnAggregatorNode;
24import tools.refinery.viatra.runtime.rete.aggregation.CountNode;
25import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode;
26import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode;
27import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode;
28import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode;
29import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlySequentialTimelyColumnAggregatorNode;
30import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode;
31import tools.refinery.viatra.runtime.rete.boundary.ExternalInputStatelessFilterNode;
32import tools.refinery.viatra.runtime.rete.eval.EvaluatorCore;
33import tools.refinery.viatra.runtime.rete.eval.MemorylessEvaluatorNode;
34import tools.refinery.viatra.runtime.rete.eval.OutputCachingEvaluatorNode;
35import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode;
36import tools.refinery.viatra.runtime.rete.index.ExistenceNode;
37import tools.refinery.viatra.runtime.rete.index.Indexer;
38import tools.refinery.viatra.runtime.rete.index.JoinNode;
39import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
40import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture;
41import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
42import tools.refinery.viatra.runtime.rete.misc.ConstantNode;
43import tools.refinery.viatra.runtime.rete.recipes.*;
44import tools.refinery.viatra.runtime.rete.single.*;
45import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
46
47import java.util.HashMap;
48import java.util.List;
49import java.util.Map;
50
51/**
52 * Factory for instantiating Rete nodes. The created nodes are not connected to the network yet.
53 *
54 * @author Bergmann Gabor
55 *
56 */
57class NodeFactory {
58 Logger logger;
59
60 public NodeFactory(Logger logger) {
61 super();
62 this.logger = logger;
63 }
64
65 /**
66 * PRE: parent node must already be created
67 */
68 public Indexer createIndexer(ReteContainer reteContainer, IndexerRecipe recipe, Supplier parentNode,
69 TraceInfo... traces) {
70
71 if (recipe instanceof ProjectionIndexerRecipe) {
72 return parentNode.constructIndex(toMask(recipe.getMask()), traces);
73 // already traced
74 } else if (recipe instanceof AggregatorIndexerRecipe) {
75 int indexOfAggregateResult = recipe.getParent().getArity();
76 int resultPosition = recipe.getMask().getSourceIndices().lastIndexOf(indexOfAggregateResult);
77
78 IAggregatorNode aggregatorNode = (IAggregatorNode) parentNode;
79 final Indexer result = (resultPosition == -1) ? aggregatorNode.getAggregatorOuterIndexer()
80 : aggregatorNode.getAggregatorOuterIdentityIndexer(resultPosition);
81
82 for (TraceInfo traceInfo : traces)
83 result.assignTraceInfo(traceInfo);
84 return result;
85 } else
86 throw new IllegalArgumentException("Unkown Indexer recipe: " + recipe);
87 }
88
89 /**
90 * PRE: recipe is not an indexer recipe.
91 */
92 public Supplier createNode(ReteContainer reteContainer, ReteNodeRecipe recipe, TraceInfo... traces) {
93 if (recipe instanceof IndexerRecipe)
94 throw new IllegalArgumentException("Indexers are not created by NodeFactory: " + recipe);
95
96 Supplier result = instantiateNodeDispatch(reteContainer, recipe);
97 for (TraceInfo traceInfo : traces)
98 result.assignTraceInfo(traceInfo);
99 return result;
100 }
101
102 private Supplier instantiateNodeDispatch(ReteContainer reteContainer, ReteNodeRecipe recipe) {
103
104 // Parentless
105
106 if (recipe instanceof ConstantRecipe)
107 return instantiateNode(reteContainer, (ConstantRecipe) recipe);
108 if (recipe instanceof InputRecipe)
109 return instantiateNode(reteContainer, (InputRecipe) recipe);
110
111 // SingleParentNodeRecipe
112
113 // if (recipe instanceof ProjectionIndexer)
114 // return instantiateNode((ProjectionIndexer)recipe);
115 if (recipe instanceof InputFilterRecipe)
116 return instantiateNode(reteContainer, (InputFilterRecipe) recipe);
117 if (recipe instanceof InequalityFilterRecipe)
118 return instantiateNode(reteContainer, (InequalityFilterRecipe) recipe);
119 if (recipe instanceof EqualityFilterRecipe)
120 return instantiateNode(reteContainer, (EqualityFilterRecipe) recipe);
121 if (recipe instanceof TransparentRecipe)
122 return instantiateNode(reteContainer, (TransparentRecipe) recipe);
123 if (recipe instanceof TrimmerRecipe)
124 return instantiateNode(reteContainer, (TrimmerRecipe) recipe);
125 if (recipe instanceof TransitiveClosureRecipe)
126 return instantiateNode(reteContainer, (TransitiveClosureRecipe) recipe);
127 if (recipe instanceof RepresentativeElectionRecipe)
128 return instantiateNode(reteContainer, (RepresentativeElectionRecipe) recipe);
129 if (recipe instanceof RelationEvaluationRecipe)
130 return instantiateNode(reteContainer, (RelationEvaluationRecipe) recipe);
131 if (recipe instanceof ExpressionEnforcerRecipe)
132 return instantiateNode(reteContainer, (ExpressionEnforcerRecipe) recipe);
133 if (recipe instanceof CountAggregatorRecipe)
134 return instantiateNode(reteContainer, (CountAggregatorRecipe) recipe);
135 if (recipe instanceof SingleColumnAggregatorRecipe)
136 return instantiateNode(reteContainer, (SingleColumnAggregatorRecipe) recipe);
137 if (recipe instanceof DiscriminatorDispatcherRecipe)
138 return instantiateNode(reteContainer, (DiscriminatorDispatcherRecipe) recipe);
139 if (recipe instanceof DiscriminatorBucketRecipe)
140 return instantiateNode(reteContainer, (DiscriminatorBucketRecipe) recipe);
141
142 // MultiParentNodeRecipe
143 if (recipe instanceof UniquenessEnforcerRecipe)
144 return instantiateNode(reteContainer, (UniquenessEnforcerRecipe) recipe);
145 if (recipe instanceof ProductionRecipe)
146 return instantiateNode(reteContainer, (ProductionRecipe) recipe);
147
148 // BetaNodeRecipe
149 if (recipe instanceof JoinRecipe)
150 return instantiateNode(reteContainer, (JoinRecipe) recipe);
151 if (recipe instanceof SemiJoinRecipe)
152 return instantiateNode(reteContainer, (SemiJoinRecipe) recipe);
153 if (recipe instanceof AntiJoinRecipe)
154 return instantiateNode(reteContainer, (AntiJoinRecipe) recipe);
155
156 // ... else
157 throw new IllegalArgumentException("Unsupported recipe type: " + recipe);
158 }
159
160 // INSTANTIATION for recipe types
161
162 private Supplier instantiateNode(ReteContainer reteContainer, InputRecipe recipe) {
163 return new ExternalInputEnumeratorNode(reteContainer);
164 }
165
166 private Supplier instantiateNode(ReteContainer reteContainer, InputFilterRecipe recipe) {
167 return new ExternalInputStatelessFilterNode(reteContainer, toMaskOrNull(recipe.getMask()));
168 }
169
170 private Supplier instantiateNode(ReteContainer reteContainer, CountAggregatorRecipe recipe) {
171 return new CountNode(reteContainer);
172 }
173
174 private Supplier instantiateNode(ReteContainer reteContainer, TransparentRecipe recipe) {
175 return new TransparentNode(reteContainer);
176 }
177
178 private Supplier instantiateNode(ReteContainer reteContainer, ExpressionEnforcerRecipe recipe) {
179 final IExpressionEvaluator evaluator = toIExpressionEvaluator(recipe.getExpression());
180 final Map<String, Integer> posMapping = toStringIndexMap(recipe.getMappedIndices());
181 final int sourceTupleWidth = recipe.getParent().getArity();
182 EvaluatorCore core = null;
183 if (recipe instanceof CheckRecipe) {
184 core = new EvaluatorCore.PredicateEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth);
185 } else if (recipe instanceof EvalRecipe) {
186 final boolean isUnwinding = ((EvalRecipe) recipe).isUnwinding();
187 core = new EvaluatorCore.FunctionEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth, isUnwinding);
188 } else {
189 throw new IllegalArgumentException("Unhandled expression enforcer recipe: " + recipe.getClass() + "!");
190 }
191 if (recipe.isCacheOutput()) {
192 return new OutputCachingEvaluatorNode(reteContainer, core);
193 } else {
194 return new MemorylessEvaluatorNode(reteContainer, core);
195 }
196 }
197
198 @SuppressWarnings({ "rawtypes", "unchecked" })
199 private Supplier instantiateNode(ReteContainer reteContainer, SingleColumnAggregatorRecipe recipe) {
200 final IMultisetAggregationOperator operator = recipe.getMultisetAggregationOperator();
201 TupleMask coreMask = null;
202 if (recipe.getOptionalMonotonicityInfo() != null) {
203 coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask());
204 } else {
205 coreMask = toMask(recipe.getGroupByMask());
206 }
207
208 if (reteContainer.isTimelyEvaluation()) {
209 final TimelyConfiguration timelyConfiguration = reteContainer.getTimelyConfiguration();
210 final AggregatorArchitecture aggregatorArchitecture = timelyConfiguration.getAggregatorArchitecture();
211 final TimelineRepresentation timelineRepresentation = timelyConfiguration.getTimelineRepresentation();
212
213 TupleMask posetMask = null;
214
215 if (recipe.getOptionalMonotonicityInfo() != null) {
216 posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask());
217 } else {
218 final int aggregatedColumn = recipe.getAggregableIndex();
219 posetMask = TupleMask.selectSingle(aggregatedColumn, coreMask.sourceWidth);
220 }
221
222 if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY
223 && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) {
224 return new FirstOnlySequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask);
225 } else if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY
226 && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) {
227 return new FirstOnlyParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask);
228 } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL
229 && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) {
230 return new FaithfulSequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask);
231 } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL
232 && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) {
233 return new FaithfulParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask);
234 } else {
235 throw new IllegalArgumentException("Unsupported timely configuration!");
236 }
237 } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) {
238 final TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask());
239 final IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo()
240 .getPosetComparator();
241 return new ColumnAggregatorNode(reteContainer, operator, recipe.isDeleteRederiveEvaluation(), coreMask,
242 posetMask, posetComparator);
243 } else {
244 final int aggregatedColumn = recipe.getAggregableIndex();
245 return new ColumnAggregatorNode(reteContainer, operator, coreMask, aggregatedColumn);
246 }
247 }
248
249 private Supplier instantiateNode(ReteContainer reteContainer, TransitiveClosureRecipe recipe) {
250 return new TransitiveClosureNode(reteContainer);
251 }
252
253 private Supplier instantiateNode(ReteContainer reteContainer, RepresentativeElectionRecipe recipe) {
254 RepresentativeElectionAlgorithm.Factory algorithmFactory = switch (recipe.getConnectivity()) {
255 case STRONG -> StronglyConnectedComponentAlgorithm::new;
256 case WEAK -> WeaklyConnectedComponentAlgorithm::new;
257 };
258 return new RepresentativeElectionNode(reteContainer, algorithmFactory);
259 }
260
261 private Supplier instantiateNode(ReteContainer reteContainer, RelationEvaluationRecipe recipe) {
262 return new RelationEvaluatorNode(reteContainer, toIRelationEvaluator(recipe.getEvaluator()));
263 }
264
265 private Supplier instantiateNode(ReteContainer reteContainer, ProductionRecipe recipe) {
266 if (reteContainer.isTimelyEvaluation()) {
267 return new TimelyProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()));
268 } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) {
269 TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask());
270 TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask());
271 IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo()
272 .getPosetComparator();
273 return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()),
274 recipe.isDeleteRederiveEvaluation(), coreMask, posetMask, posetComparator);
275 } else {
276 return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()),
277 recipe.isDeleteRederiveEvaluation());
278 }
279 }
280
281 private Supplier instantiateNode(ReteContainer reteContainer, UniquenessEnforcerRecipe recipe) {
282 if (reteContainer.isTimelyEvaluation()) {
283 return new TimelyUniquenessEnforcerNode(reteContainer, recipe.getArity());
284 } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) {
285 TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask());
286 TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask());
287 IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo()
288 .getPosetComparator();
289 return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation(),
290 coreMask, posetMask, posetComparator);
291 } else {
292 return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation());
293 }
294 }
295
296 private Supplier instantiateNode(ReteContainer reteContainer, ConstantRecipe recipe) {
297 final List<Object> constantValues = recipe.getConstantValues();
298 final Object[] constantArray = constantValues.toArray(new Object[constantValues.size()]);
299 return new ConstantNode(reteContainer, Tuples.flatTupleOf(constantArray));
300 }
301
302 private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorBucketRecipe recipe) {
303 return new DiscriminatorBucketNode(reteContainer, recipe.getBucketKey());
304 }
305
306 private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorDispatcherRecipe recipe) {
307 return new DiscriminatorDispatcherNode(reteContainer, recipe.getDiscriminationColumnIndex());
308 }
309
310 private Supplier instantiateNode(ReteContainer reteContainer, TrimmerRecipe recipe) {
311 return new TrimmerNode(reteContainer, toMask(recipe.getMask()));
312 }
313
314 private Supplier instantiateNode(ReteContainer reteContainer, InequalityFilterRecipe recipe) {
315 Tunnel result = new InequalityFilterNode(reteContainer, recipe.getSubject(),
316 TupleMask.fromSelectedIndices(recipe.getParent().getArity(), recipe.getInequals()));
317 return result;
318 }
319
320 private Supplier instantiateNode(ReteContainer reteContainer, EqualityFilterRecipe recipe) {
321 final int[] equalIndices = TupleMask.integersToIntArray(recipe.getIndices());
322 return new EqualityFilterNode(reteContainer, equalIndices);
323 }
324
325 private Supplier instantiateNode(ReteContainer reteContainer, AntiJoinRecipe recipe) {
326 return new ExistenceNode(reteContainer, true);
327 }
328
329 private Supplier instantiateNode(ReteContainer reteContainer, SemiJoinRecipe recipe) {
330 return new ExistenceNode(reteContainer, false);
331 }
332
333 private Supplier instantiateNode(ReteContainer reteContainer, JoinRecipe recipe) {
334 return new JoinNode(reteContainer, toMask(recipe.getRightParentComplementaryMask()));
335 }
336
337 // HELPERS
338
339 private IExpressionEvaluator toIExpressionEvaluator(ExpressionDefinition expressionDefinition) {
340 final Object evaluator = expressionDefinition.getEvaluator();
341 if (evaluator instanceof IExpressionEvaluator) {
342 return (IExpressionEvaluator) evaluator;
343 }
344 throw new IllegalArgumentException("No runtime support for expression evaluator: " + evaluator);
345 }
346
347 private IRelationEvaluator toIRelationEvaluator(ExpressionDefinition expressionDefinition) {
348 final Object evaluator = expressionDefinition.getEvaluator();
349 if (evaluator instanceof IRelationEvaluator) {
350 return (IRelationEvaluator) evaluator;
351 }
352 throw new IllegalArgumentException("No runtime support for relation evaluator: " + evaluator);
353 }
354
355 private Map<String, Integer> toStringIndexMap(final EMap<String, Integer> mappedIndices) {
356 final HashMap<String, Integer> result = new HashMap<String, Integer>();
357 for (java.util.Map.Entry<String, Integer> entry : mappedIndices) {
358 result.put(entry.getKey(), entry.getValue());
359 }
360 return result;
361 }
362
363 /** Mask can be null */
364 private TupleMask toMaskOrNull(Mask mask) {
365 if (mask == null)
366 return null;
367 else
368 return toMask(mask);
369 }
370
371 /** Mask is non-null. */
372 private TupleMask toMask(Mask mask) {
373 return TupleMask.fromSelectedIndices(mask.getSourceArity(), mask.getSourceIndices());
374 }
375
376}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java
new file mode 100644
index 00000000..9121fc44
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java
@@ -0,0 +1,346 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
16import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
17import tools.refinery.viatra.runtime.rete.boundary.InputConnector;
18import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper;
19import tools.refinery.viatra.runtime.rete.index.Indexer;
20import tools.refinery.viatra.runtime.rete.index.OnetimeIndexer;
21import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
22import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand;
23import tools.refinery.viatra.runtime.rete.recipes.*;
24import tools.refinery.viatra.runtime.rete.recipes.helper.RecipeRecognizer;
25import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper;
26import tools.refinery.viatra.runtime.rete.remote.Address;
27import tools.refinery.viatra.runtime.rete.remote.RemoteReceiver;
28import tools.refinery.viatra.runtime.rete.remote.RemoteSupplier;
29import tools.refinery.viatra.runtime.rete.traceability.ActiveNodeConflictTrace;
30import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
31import tools.refinery.viatra.runtime.rete.traceability.UserRequestTrace;
32import tools.refinery.viatra.runtime.rete.util.Options;
33
34import java.util.Map;
35import java.util.Set;
36
37/**
38 * Stores the internal parts of a rete network. Nodes are stored according to type and parameters.
39 *
40 * @author Gabor Bergmann
41 */
42public class NodeProvisioner {
43
44 // boolean activeStorage = true;
45
46 ReteContainer reteContainer;
47 NodeFactory nodeFactory;
48 ConnectionFactory connectionFactory;
49 InputConnector inputConnector;
50 IQueryRuntimeContext runtimeContext;
51
52 // TODO as recipe?
53 Map<Supplier, RemoteReceiver> remoteReceivers = CollectionsFactory.createMap();
54 Map<Address<? extends Supplier>, RemoteSupplier> remoteSuppliers = CollectionsFactory.createMap();
55
56 private RecipeRecognizer recognizer;
57
58 /**
59 * PRE: NodeFactory, ConnectionFactory must exist
60 *
61 * @param reteContainer
62 * the ReteNet whose interior is to be mapped.
63 */
64 public NodeProvisioner(ReteContainer reteContainer) {
65 super();
66 this.reteContainer = reteContainer;
67 this.nodeFactory = reteContainer.getNodeFactory();
68 this.connectionFactory = reteContainer.getConnectionFactory();
69 this.inputConnector = reteContainer.getInputConnectionFactory();
70 runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext();
71 recognizer = new RecipeRecognizer(runtimeContext);
72 }
73
74 public synchronized Address<? extends Node> getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) {
75 ReteNodeRecipe recipe = recipeTrace.getRecipe();
76 Address<? extends Node> result = getNodesByRecipe().get(recipe);
77 if (result != null) {
78 // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace
79 if (getRecipeTraces().add(recipeTrace))
80 result.getNodeCache().assignTraceInfo(recipeTrace);
81 } else {
82 // No node for this recipe object - but equivalent recipes still
83 // reusable
84 ReteNodeRecipe canonicalRecipe = recognizer.canonicalizeRecipe(recipe);
85 if (canonicalRecipe != recipe) {
86 // FOUND EQUIVALENT RECIPE
87 result = getNodesByRecipe().get(canonicalRecipe);
88 if (result != null) {
89 // NODE ALREADY CONSTRUCTED FOR EQUIVALENT RECIPE
90 recipeTrace.shadowWithEquivalentRecipe(canonicalRecipe);
91 getNodesByRecipe().put(recipe, result);
92 if (getRecipeTraces().add(recipeTrace))
93 result.getNodeCache().assignTraceInfo(recipeTrace);
94 // Bug 491922: ensure that recipe shadowing propagates to
95 // parent traces
96 // note that if equivalentRecipes() becomes more
97 // sophisticated
98 // and considers recipes with different parents, this might
99 // have to be changed
100 ensureParents(recipeTrace);
101 } else {
102 // CONSTRUCTION IN PROGRESS FOR EQUIVALENT RECIPE
103 if (recipe instanceof IndexerRecipe) {
104 // this is allowed for indexers;
105 // go on with the construction, as the same indexer node
106 // will be obtained anyways
107 } else {
108 throw new IllegalStateException(
109 "This should not happen: " + "non-indexer nodes are are supposed to be constructed "
110 + "as soon as they are designated as canonical recipes");
111 }
112 }
113 }
114 if (result == null) {
115 // MUST INSTANTIATE NEW NODE FOR RECIPE
116 final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe);
117 result = reteContainer.makeAddress(freshNode);
118 }
119 }
120 return result;
121 }
122
123 private Set<RecipeTraceInfo> getRecipeTraces() {
124 return reteContainer.network.recipeTraces;
125 }
126
127 private Node instantiateNodeForRecipe(RecipeTraceInfo recipeTrace, final ReteNodeRecipe recipe) {
128 this.getRecipeTraces().add(recipeTrace);
129 if (recipe instanceof IndexerRecipe) {
130
131 // INSTANTIATE AND HOOK UP
132 // (cannot delay hooking up, because parent determines indexer
133 // implementation)
134 ensureParents(recipeTrace);
135 final ReteNodeRecipe parentRecipe = recipeTrace.getParentRecipeTraces().iterator().next().getRecipe();
136 final Indexer result = nodeFactory.createIndexer(reteContainer, (IndexerRecipe) recipe,
137 asSupplier(
138 (Address<? extends Supplier>) reteContainer.network.getExistingNodeByRecipe(parentRecipe)),
139 recipeTrace);
140
141 // REMEMBER
142 if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) {
143 getNodesByRecipe().put(recipe, reteContainer.makeAddress(result));
144 }
145
146 return result;
147 } else {
148
149 // INSTANTIATE
150 Node result = nodeFactory.createNode(reteContainer, recipe, recipeTrace);
151
152 // REMEMBER
153 if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) {
154 getNodesByRecipe().put(recipe, reteContainer.makeAddress(result));
155 }
156
157 // HOOK UP
158 // (recursion-tolerant due to this delayed order of initialization)
159 if (recipe instanceof InputRecipe) {
160 inputConnector.connectInput((InputRecipe) recipe, result);
161 } else {
162 if (recipe instanceof InputFilterRecipe)
163 inputConnector.connectInputFilter((InputFilterRecipe) recipe, result);
164 ensureParents(recipeTrace);
165 connectionFactory.connectToParents(recipeTrace, result);
166 }
167 return result;
168 }
169 }
170
171 private Map<ReteNodeRecipe, Address<? extends Node>> getNodesByRecipe() {
172 return reteContainer.network.nodesByRecipe;
173 }
174
175 private void ensureParents(RecipeTraceInfo recipeTrace) {
176 for (RecipeTraceInfo parentTrace : recipeTrace.getParentRecipeTraces()) {
177 getOrCreateNodeByRecipe(parentTrace);
178 }
179 }
180
181 //// Remoting - TODO eliminate?
182
183 synchronized RemoteReceiver accessRemoteReceiver(Address<? extends Supplier> address) {
184 throw new UnsupportedOperationException("Multi-container Rete not supported yet");
185 // if (!reteContainer.isLocal(address))
186 // return
187 // address.getContainer().getProvisioner().accessRemoteReceiver(address);
188 // Supplier localSupplier = reteContainer.resolveLocal(address);
189 // RemoteReceiver result = remoteReceivers.get(localSupplier);
190 // if (result == null) {
191 // result = new RemoteReceiver(reteContainer);
192 // reteContainer.connect(localSupplier, result); // stateless node, no
193 // // synch required
194 //
195 // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
196 // remoteReceivers.put(localSupplier, result);
197 // }
198 // return result;
199 }
200
201 /**
202 * @pre: address is NOT local
203 */
204 synchronized RemoteSupplier accessRemoteSupplier(Address<? extends Supplier> address) {
205 throw new UnsupportedOperationException("Multi-container Rete not supported yet");
206 // RemoteSupplier result = remoteSuppliers.get(address);
207 // if (result == null) {
208 // result = new RemoteSupplier(reteContainer,
209 // address.getContainer().getProvisioner()
210 // .accessRemoteReceiver(address));
211 // // network.connectAndSynchronize(supplier, result);
212 //
213 // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
214 // remoteSuppliers.put(address, result);
215 // }
216 // return result;
217 }
218
219 /**
220 * The powerful method for accessing any (supplier) Address as a local supplier.
221 */
222 public Supplier asSupplier(Address<? extends Supplier> address) {
223 if (!reteContainer.isLocal(address))
224 return accessRemoteSupplier(address);
225 else
226 return reteContainer.resolveLocal(address);
227 }
228
229 /** the composite key tuple is formed as (RecipeTraceInfo, TupleMask) */
230 private Map<Tuple, UserRequestTrace> projectionIndexerUserRequests = CollectionsFactory.createMap();
231
232 // local version
233 // TODO remove?
234 public synchronized ProjectionIndexer accessProjectionIndexer(RecipeTraceInfo productionTrace, TupleMask mask) {
235 Tuple tableKey = Tuples.staticArityFlatTupleOf(productionTrace, mask);
236 UserRequestTrace indexerTrace = projectionIndexerUserRequests.computeIfAbsent(tableKey, k -> {
237 final ProjectionIndexerRecipe projectionIndexerRecipe = projectionIndexerRecipe(
238 productionTrace, mask);
239 return new UserRequestTrace(projectionIndexerRecipe, productionTrace);
240 });
241 final Address<? extends Node> address = getOrCreateNodeByRecipe(indexerTrace);
242 return (ProjectionIndexer) reteContainer.resolveLocal(address);
243 }
244
245 // local version
246 public synchronized ProjectionIndexer accessProjectionIndexerOnetime(RecipeTraceInfo supplierTrace,
247 TupleMask mask) {
248 if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
249 return accessProjectionIndexer(supplierTrace, mask);
250
251 final Address<? extends Node> supplierAddress = getOrCreateNodeByRecipe(supplierTrace);
252 Supplier supplier = (Supplier) reteContainer.resolveLocal(supplierAddress);
253
254 OnetimeIndexer result = new OnetimeIndexer(reteContainer, mask);
255 reteContainer.getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, result, reteContainer));
256
257 return result;
258 }
259
260 // local, read-only version
261 public synchronized ProjectionIndexer peekProjectionIndexer(RecipeTraceInfo supplierTrace, TupleMask mask) {
262 final Address<? extends Node> address = getNodesByRecipe().get(projectionIndexerRecipe(supplierTrace, mask));
263 return address == null ? null : (ProjectionIndexer) reteContainer.resolveLocal(address);
264 }
265
266 private ProjectionIndexerRecipe projectionIndexerRecipe(
267 RecipeTraceInfo parentTrace, TupleMask mask) {
268 final ReteNodeRecipe parentRecipe = parentTrace.getRecipe();
269 Tuple tableKey = Tuples.staticArityFlatTupleOf(parentRecipe, mask);
270 ProjectionIndexerRecipe projectionIndexerRecipe = resultSeedRecipes.computeIfAbsent(tableKey, k ->
271 RecipesHelper.projectionIndexerRecipe(parentRecipe, CompilerHelper.toRecipeMask(mask))
272 );
273 return projectionIndexerRecipe;
274 }
275
276 /** the composite key tuple is formed as (ReteNodeRecipe, TupleMask) */
277 private Map<Tuple, ProjectionIndexerRecipe> resultSeedRecipes = CollectionsFactory.createMap();
278
279 // public synchronized Address<? extends Supplier>
280 // accessValueBinderFilterNode(
281 // Address<? extends Supplier> supplierAddress, int bindingIndex, Object
282 // bindingValue) {
283 // Supplier supplier = asSupplier(supplierAddress);
284 // Object[] paramsArray = { supplier.getNodeId(), bindingIndex, bindingValue
285 // };
286 // Tuple params = new FlatTuple(paramsArray);
287 // ValueBinderFilterNode result = valueBinderFilters.get(params);
288 // if (result == null) {
289 // result = new ValueBinderFilterNode(reteContainer, bindingIndex,
290 // bindingValue);
291 // reteContainer.connect(supplier, result); // stateless node, no synch
292 // // required
293 //
294 // if (Options.nodeSharingOption == Options.NodeSharingOption.ALL)
295 // valueBinderFilters.put(params, result);
296 // }
297 // return reteContainer.makeAddress(result);
298 // }
299
300 /**
301 * Returns a copy of the given indexer that is an active node by itself (created if does not exist). (Convention:
302 * attached with same mask to a transparent node that is attached to parent node.) Node is created if it does not
303 * exist yet.
304 *
305 * @return an identical but active indexer
306 */
307 // TODO rethink traceability
308 RecipeTraceInfo accessActiveIndexer(RecipeTraceInfo inactiveIndexerRecipeTrace) {
309 final RecipeTraceInfo parentRecipeTrace = inactiveIndexerRecipeTrace.getParentRecipeTraces().iterator().next();
310 final ProjectionIndexerRecipe inactiveIndexerRecipe = (ProjectionIndexerRecipe) inactiveIndexerRecipeTrace
311 .getRecipe();
312
313 final TransparentRecipe transparentRecipe = RecipesFactory.eINSTANCE.createTransparentRecipe();
314 transparentRecipe.setParent(parentRecipeTrace.getRecipe());
315 final ActiveNodeConflictTrace transparentRecipeTrace = new ActiveNodeConflictTrace(transparentRecipe,
316 parentRecipeTrace, inactiveIndexerRecipeTrace);
317
318 final ProjectionIndexerRecipe activeIndexerRecipe = RecipesFactory.eINSTANCE
319 .createProjectionIndexerRecipe();
320 activeIndexerRecipe.setParent(transparentRecipe);
321 activeIndexerRecipe.setMask(inactiveIndexerRecipe.getMask());
322 final ActiveNodeConflictTrace activeIndexerRecipeTrace = new ActiveNodeConflictTrace(activeIndexerRecipe,
323 transparentRecipeTrace, inactiveIndexerRecipeTrace);
324
325 return activeIndexerRecipeTrace;
326 }
327
328 // /**
329 // * @param parent
330 // * @return
331 // */
332 // private TransparentNode accessTransparentNodeInternal(Supplier parent) {
333 // nodeFactory.
334 // return null;
335 // }
336
337 // public synchronized void registerSpecializedProjectionIndexer(Node node,
338 // ProjectionIndexer indexer) {
339 // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) {
340 // Object[] paramsArray = { node.getNodeId(), indexer.getMask() };
341 // Tuple params = new FlatTuple(paramsArray);
342 // projectionIndexers.put(params, indexer);
343 // }
344 // }
345
346}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java
new file mode 100644
index 00000000..1eaa18e7
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network;
10
11import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
14import tools.refinery.viatra.runtime.matchers.util.Direction;
15
16/**
17 * @author Tamas Szabo
18 * @since 2.0
19 */
20public interface PosetAwareReceiver extends Receiver {
21
22 public TupleMask getCoreMask();
23
24 public TupleMask getPosetMask();
25
26 public IPosetComparator getPosetComparator();
27
28 /**
29 * Updates the receiver with a newly found or lost partial matching also providing information
30 * whether the update is a monotone change or not.
31 *
32 * @param direction the direction of the update
33 * @param update the update tuple
34 * @param monotone true if the update is monotone, false otherwise
35 * @since 2.4
36 */
37 public void updateWithPosetInfo(Direction direction, Tuple update, boolean monotone);
38
39}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java
new file mode 100644
index 00000000..211194c0
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15
16/**
17 * Interface intended for nodes containing complete matches.
18 *
19 * @author Gabor Bergmann
20 */
21public interface ProductionNode extends Tunnel, Iterable<Tuple> {
22
23 /**
24 * @return the position mapping of this particular pattern that maps members of the tuple type to their positions
25 */
26 Map<String, Integer> getPosMapping();
27
28}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java
new file mode 100644
index 00000000..3dc9aad7
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
19import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
20
21/**
22 * ALL METHODS: FOR INTERNAL USE ONLY; ONLY INVOKE FROM {@link ReteContainer}
23 *
24 * @author Gabor Bergmann
25 * @noimplement This interface is not intended to be implemented by external clients.
26 */
27public interface Receiver extends Node {
28
29 /**
30 * Updates the receiver with a newly found or lost partial matching.
31 *
32 * @since 2.4
33 */
34 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp);
35
36 /**
37 * Updates the receiver in batch style with a collection of updates. The input collection consists of pairs in the
38 * form (t, c) where t is an update tuple and c is the count. The count can also be negative, and it specifies how
39 * many times the tuple t gets deleted or inserted. The default implementation of this method simply calls
40 * {@link #update(Direction, Tuple, Timestamp)} individually for all updates.
41 *
42 * @since 2.8
43 */
44 public default void batchUpdate(final Collection<Map.Entry<Tuple, Integer>> updates, final Timestamp timestamp) {
45 for (final Entry<Tuple, Integer> entry : updates) {
46 int count = entry.getValue();
47
48 Direction direction;
49 if (count < 0) {
50 direction = Direction.DELETE;
51 count = -count;
52 } else {
53 direction = Direction.INSERT;
54 }
55
56 for (int i = 0; i < count; i++) {
57 update(direction, entry.getKey(), timestamp);
58 }
59 }
60 }
61
62 /**
63 * Returns the {@link Mailbox} of this receiver.
64 *
65 * @return the mailbox
66 * @since 2.0
67 */
68 public Mailbox getMailbox();
69
70 /**
71 * appends a parent that will continuously send insert and revoke updates to this supplier
72 */
73 void appendParent(final Supplier supplier);
74
75 /**
76 * removes a parent
77 */
78 void removeParent(final Supplier supplier);
79
80 /**
81 * access active parent
82 */
83 Collection<Supplier> getParents();
84
85}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java
new file mode 100644
index 00000000..cae78d37
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network;
10
11/**
12 * A rederivable node can potentially re-derive tuples after the Rete network has finished the delivery of messages.
13 *
14 * @author Tamas Szabo
15 * @since 1.6
16 */
17public interface RederivableNode extends Node, IGroupable {
18
19 /**
20 * The method is called by the {@link ReteContainer} to re-derive tuples after the normal messages have been
21 * delivered and consumed. The re-derivation process may trigger the creation and delivery of further messages
22 * and further re-derivation rounds.
23 */
24 public void rederiveOne();
25
26 /**
27 * Returns true if this node actually runs in DRed mode (not necessarily).
28 *
29 * @return true if the node is operating in DRed mode
30 * @since 2.0
31 */
32 public boolean isInDRedMode();
33
34}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java
new file mode 100644
index 00000000..09bff29e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java
@@ -0,0 +1,14 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.rete.network;
7
8import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
9
10import java.util.Collection;
11
12public interface ReinitializedNode {
13 void reinitializeWith(Collection<Tuple> tuples);
14}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java
new file mode 100644
index 00000000..79e0526d
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java
@@ -0,0 +1,729 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10
11package tools.refinery.viatra.runtime.rete.network;
12
13import org.apache.log4j.Logger;
14import tools.refinery.viatra.runtime.CancellationToken;
15import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.Clearable;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
21import tools.refinery.viatra.runtime.rete.boundary.InputConnector;
22import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
23import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
24import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker;
27import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationTracker;
28import tools.refinery.viatra.runtime.rete.network.delayed.DelayedCommand;
29import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand;
30import tools.refinery.viatra.runtime.rete.network.delayed.DelayedDisconnectCommand;
31import tools.refinery.viatra.runtime.rete.remote.Address;
32import tools.refinery.viatra.runtime.rete.single.SingleInputNode;
33import tools.refinery.viatra.runtime.rete.single.TrimmerNode;
34import tools.refinery.viatra.runtime.rete.util.Options;
35
36import java.util.*;
37import java.util.function.Function;
38
39/**
40 * @author Gabor Bergmann
41 *
42 * Mutexes: externalMessageLock - enlisting messages into and retrieving from the external message queue
43 * @since 2.2
44 */
45public final class ReteContainer {
46
47 protected Thread consumerThread = null;
48 protected boolean killed = false;
49
50 protected Network network;
51
52 protected LinkedList<Clearable> clearables;
53 protected Map<Long, Node> nodesById;
54 protected long nextId = 0;
55
56 protected ConnectionFactory connectionFactory;
57 protected NodeProvisioner nodeProvisioner;
58
59 protected Deque<UpdateMessage> internalMessageQueue = new ArrayDeque<UpdateMessage>();
60 protected/* volatile */Deque<UpdateMessage> externalMessageQueue = new ArrayDeque<UpdateMessage>();
61 protected Object externalMessageLock = new Object();
62 protected Long clock = 1L; // even: steady state, odd: active queue; access
63 // ONLY with messageQueue locked!
64 protected Map<ReteContainer, Long> terminationCriteria = null;
65 protected final Logger logger;
66 protected final CommunicationTracker tracker;
67
68 protected final IQueryBackendContext backendContext;
69
70 protected Set<DelayedCommand> delayedCommandQueue;
71 protected Set<DelayedCommand> delayedCommandBuffer;
72 protected boolean executingDelayedCommands;
73
74 protected final TimelyConfiguration timelyConfiguration;
75
76 private final CancellationToken cancellationToken;
77
78 /**
79 * @param threaded
80 * false if operating in a single-threaded environment
81 */
82 public ReteContainer(Network network, boolean threaded) {
83 super();
84 this.network = network;
85 this.backendContext = network.getEngine().getBackendContext();
86 this.timelyConfiguration = network.getEngine().getTimelyConfiguration();
87 cancellationToken = backendContext.getRuntimeContext().getCancellationToken();
88
89 this.delayedCommandQueue = new LinkedHashSet<DelayedCommand>();
90 this.delayedCommandBuffer = new LinkedHashSet<DelayedCommand>();
91 this.executingDelayedCommands = false;
92
93 if (this.isTimelyEvaluation()) {
94 this.tracker = new TimelyCommunicationTracker(this.getTimelyConfiguration());
95 } else {
96 this.tracker = new TimelessCommunicationTracker();
97 }
98
99 this.nodesById = CollectionsFactory.createMap();
100 this.clearables = new LinkedList<Clearable>();
101 this.logger = network.getEngine().getLogger();
102
103 this.connectionFactory = new ConnectionFactory(this);
104 this.nodeProvisioner = new NodeProvisioner(this);
105
106 if (threaded) {
107 this.terminationCriteria = CollectionsFactory.createMap();
108 this.consumerThread = new Thread("Rete thread of " + ReteContainer.super.toString()) {
109 @Override
110 public void run() {
111 messageConsumptionCycle();
112 }
113 };
114 this.consumerThread.start();
115 }
116 }
117
118 /**
119 * @since 2.4
120 */
121 public boolean isTimelyEvaluation() {
122 return this.timelyConfiguration != null;
123 }
124
125 /**
126 * @since 2.4
127 */
128 public TimelyConfiguration getTimelyConfiguration() {
129 return this.timelyConfiguration;
130 }
131
132 /**
133 * @since 1.6
134 * @return the communication graph of the nodes, incl. message scheduling
135 */
136 public CommunicationTracker getCommunicationTracker() {
137 return tracker;
138 }
139
140 /**
141 * Stops this container. To be called by Network.kill()
142 */
143 public void kill() {
144 killed = true;
145 if (consumerThread != null)
146 consumerThread.interrupt();
147 }
148
149 /**
150 * Establishes connection between a supplier and a receiver node, regardless which container they are in. Assumption
151 * is that this container is the home of the receiver, but it is not strictly necessary.
152 *
153 * @param synchronise
154 * indicates whether the receiver should be synchronised to the current contents of the supplier
155 */
156 public void connectRemoteNodes(Address<? extends Supplier> supplier, Address<? extends Receiver> receiver,
157 boolean synchronise) {
158 if (!isLocal(receiver))
159 receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise);
160 else {
161 Receiver child = resolveLocal(receiver);
162 connectRemoteSupplier(supplier, child, synchronise);
163 }
164 }
165
166 /**
167 * Severs connection between a supplier and a receiver node, regardless which container they are in. Assumption is
168 * that this container is the home of the receiver, but it is not strictly necessary.
169 *
170 * @param desynchronise
171 * indicates whether the current contents of the supplier should be subtracted from the receiver
172 */
173 public void disconnectRemoteNodes(Address<? extends Supplier> supplier, Address<? extends Receiver> receiver,
174 boolean desynchronise) {
175 if (!isLocal(receiver))
176 receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise);
177 else {
178 Receiver child = resolveLocal(receiver);
179 disconnectRemoteSupplier(supplier, child, desynchronise);
180 }
181 }
182
183 /**
184 * Establishes connection between a remote supplier and a local receiver node.
185 *
186 * @param synchronise
187 * indicates whether the receiver should be synchronised to the current contents of the supplier
188 */
189 public void connectRemoteSupplier(Address<? extends Supplier> supplier, Receiver receiver, boolean synchronise) {
190 Supplier parent = nodeProvisioner.asSupplier(supplier);
191 if (synchronise)
192 connectAndSynchronize(parent, receiver);
193 else
194 connect(parent, receiver);
195 }
196
197 /**
198 * Severs connection between a remote supplier and a local receiver node.
199 *
200 * @param desynchronise
201 * indicates whether the current contents of the supplier should be subtracted from the receiver
202 */
203 public void disconnectRemoteSupplier(Address<? extends Supplier> supplier, Receiver receiver,
204 boolean desynchronise) {
205 Supplier parent = nodeProvisioner.asSupplier(supplier);
206 if (desynchronise)
207 disconnectAndDesynchronize(parent, receiver);
208 else
209 disconnect(parent, receiver);
210 }
211
212 /**
213 * Connects a receiver to a supplier
214 */
215 public void connect(Supplier supplier, Receiver receiver) {
216 supplier.appendChild(receiver);
217 receiver.appendParent(supplier);
218 tracker.registerDependency(supplier, receiver);
219 }
220
221 /**
222 * Disconnects a receiver from a supplier
223 */
224 public void disconnect(Supplier supplier, Receiver receiver) {
225 supplier.removeChild(receiver);
226 receiver.removeParent(supplier);
227 tracker.unregisterDependency(supplier, receiver);
228 }
229
230 /**
231 * @since 2.3
232 */
233 public boolean isExecutingDelayedCommands() {
234 return this.executingDelayedCommands;
235 }
236
237 /**
238 * @since 2.3
239 */
240 public Set<DelayedCommand> getDelayedCommandQueue() {
241 if (this.executingDelayedCommands) {
242 return this.delayedCommandBuffer;
243 } else {
244 return this.delayedCommandQueue;
245 }
246 }
247
248 /**
249 * Connects a receiver to a remote supplier, and synchronizes it to the current contents of the supplier
250 */
251 public void connectAndSynchronize(Supplier supplier, Receiver receiver) {
252 supplier.appendChild(receiver);
253 receiver.appendParent(supplier);
254 tracker.registerDependency(supplier, receiver);
255 getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, receiver, this));
256 }
257
258 /**
259 * Disconnects a receiver from a supplier
260 */
261 public void disconnectAndDesynchronize(Supplier supplier, Receiver receiver) {
262 final boolean wasInSameSCC = this.isTimelyEvaluation() && this.tracker.areInSameGroup(supplier, receiver);
263 supplier.removeChild(receiver);
264 receiver.removeParent(supplier);
265 tracker.unregisterDependency(supplier, receiver);
266 getDelayedCommandQueue().add(new DelayedDisconnectCommand(supplier, receiver, this, wasInSameSCC));
267 }
268
269 /**
270 * @since 2.3
271 */
272 public void executeDelayedCommands() {
273 if (!this.delayedCommandQueue.isEmpty()) {
274 flushUpdates();
275 this.executingDelayedCommands = true;
276 for (final DelayedCommand command : this.delayedCommandQueue) {
277 command.run();
278 }
279 this.delayedCommandQueue = this.delayedCommandBuffer;
280 this.delayedCommandBuffer = new LinkedHashSet<DelayedCommand>();
281 flushUpdates();
282 this.executingDelayedCommands = false;
283 }
284 }
285
286 /**
287 * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is
288 * indicated by the Address. Designed to be called by the Network, DO NOT use in any other way. @pre:
289 * address.container == this, e.g. address MUST be local
290 *
291 * @return the value of the container's clock at the time when the message was accepted into the local message queue
292 */
293 long sendUpdateToLocalAddress(Address<? extends Receiver> address, Direction direction, Tuple updateElement) {
294 long timestamp;
295 Receiver receiver = resolveLocal(address);
296 UpdateMessage message = new UpdateMessage(receiver, direction, updateElement);
297 synchronized (externalMessageLock) {
298 externalMessageQueue.add(message);
299 timestamp = clock;
300 externalMessageLock.notifyAll();
301 }
302
303 return timestamp;
304
305 }
306
307 /**
308 * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial
309 * matching. The receiver is indicated by the Address. Designed to be called by the Network, DO NOT use in any other
310 * way. @pre: address.container == this, e.g. address MUST be local @pre: updateElements is nonempty!
311 *
312 * @return the value of the container's clock at the time when the message was accepted into the local message queue
313 */
314 long sendUpdatesToLocalAddress(Address<? extends Receiver> address, Direction direction,
315 Collection<Tuple> updateElements) {
316
317 long timestamp;
318 Receiver receiver = resolveLocal(address);
319 // UpdateMessage message = new UpdateMessage(receiver, direction,
320 // updateElement);
321 synchronized (externalMessageLock) {
322 for (Tuple ps : updateElements)
323 externalMessageQueue.add(new UpdateMessage(receiver, direction, ps));
324 // messageQueue.add(new UpdateMessage(resolveLocal(address),
325 // direction, updateElement));
326 // this.sendUpdateInternal(resolveLocal(address), direction,
327 // updateElement);
328 timestamp = clock;
329 externalMessageLock.notifyAll();
330 }
331
332 return timestamp;
333 }
334
335 /**
336 * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is
337 * indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT use in any
338 * other way.
339 */
340 void sendUpdateToLocalAddressSingleThreaded(Address<? extends Receiver> address, Direction direction,
341 Tuple updateElement) {
342 Receiver receiver = resolveLocal(address);
343 UpdateMessage message = new UpdateMessage(receiver, direction, updateElement);
344 internalMessageQueue.add(message);
345 }
346
347 /**
348 * Sends multiple update messages to the receiver node, indicating a newly found or lost partial matching. The
349 * receiver is indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT
350 * use in any other way.
351 *
352 * @pre: address.container == this, e.g. address MUST be local
353 */
354 void sendUpdatesToLocalAddressSingleThreaded(Address<? extends Receiver> address, Direction direction,
355 Collection<Tuple> updateElements) {
356 Receiver receiver = resolveLocal(address);
357 for (Tuple ps : updateElements)
358 internalMessageQueue.add(new UpdateMessage(receiver, direction, ps));
359 }
360
361 /**
362 * Sends an update message to a node in a different container. The receiver is indicated by the Address. Designed to
363 * be called by RemoteReceivers, DO NOT use in any other way.
364 *
365 * @since 2.4
366 */
367 public void sendUpdateToRemoteAddress(Address<? extends Receiver> address, Direction direction,
368 Tuple updateElement) {
369 ReteContainer otherContainer = address.getContainer();
370 long otherClock = otherContainer.sendUpdateToLocalAddress(address, direction, updateElement);
371 // Long criterion = terminationCriteria.get(otherContainer);
372 // if (criterion==null || otherClock > criterion)
373 terminationCriteria.put(otherContainer, otherClock);
374 }
375
376 /**
377 * Finalises all update sequences and returns. To be called from user threads (e.g. network construction).
378 */
379 public void flushUpdates() {
380 network.waitForReteTermination();
381 // synchronized (messageQueue)
382 // {
383 // while (!messageQueue.isEmpty())
384 // {
385 // try {
386 // UpdateMessage message = messageQueue.take();
387 // message.receiver.update(message.direction, message.updateElement);
388 // } catch (InterruptedException e) {}
389 // }
390 // }
391 }
392
393 /**
394 * Retrieves a safe copy of the contents of a supplier.
395 *
396 * <p> Note that there may be multiple copies of a Tuple in case of a {@link TrimmerNode}, so the result is not always a set.
397 *
398 * @param flush if true, a flush is performed before pulling the contents
399 * @since 2.3
400 */
401 public Collection<Tuple> pullContents(final Supplier supplier, final boolean flush) {
402 if (flush) {
403 flushUpdates();
404 }
405 final Collection<Tuple> collector = new ArrayList<Tuple>();
406 supplier.pullInto(collector, flush);
407 return collector;
408 }
409
410 /**
411 * @since 2.4
412 */
413 public Map<Tuple, Timeline<Timestamp>> pullContentsWithTimeline(final Supplier supplier, final boolean flush) {
414 if (flush) {
415 flushUpdates();
416 }
417 final Map<Tuple, Timeline<Timestamp>> collector = CollectionsFactory.createMap();
418 supplier.pullIntoWithTimeline(collector, flush);
419 return collector;
420 }
421
422 /**
423 * Retrieves the contents of a SingleInputNode's parentage.
424 *
425 * @since 2.3
426 */
427 public Collection<Tuple> pullPropagatedContents(final SingleInputNode supplier, final boolean flush) {
428 if (flush) {
429 flushUpdates();
430 }
431 final Collection<Tuple> collector = new LinkedList<Tuple>();
432 supplier.propagatePullInto(collector, flush);
433 return collector;
434 }
435
436 /**
437 * Retrieves the timestamp-aware contents of a SingleInputNode's parentage.
438 *
439 * @since 2.3
440 */
441 public Map<Tuple, Timeline<Timestamp>> pullPropagatedContentsWithTimestamp(final SingleInputNode supplier,
442 final boolean flush) {
443 if (flush) {
444 flushUpdates();
445 }
446 final Map<Tuple, Timeline<Timestamp>> collector = CollectionsFactory.createMap();
447 supplier.propagatePullIntoWithTimestamp(collector, flush);
448 return collector;
449 }
450
451 /**
452 * Retrieves the contents of a supplier for a remote caller. Assumption is that this container is the home of the
453 * supplier, but it is not strictly necessary.
454 *
455 * @param supplier
456 * the address of the supplier to be pulled.
457 * @since 2.3
458 */
459 public Collection<Tuple> remotePull(Address<? extends Supplier> supplier, boolean flush) {
460 if (!isLocal(supplier))
461 return supplier.getContainer().remotePull(supplier, flush);
462 return pullContents(resolveLocal(supplier), flush);
463 }
464
465 /**
466 * Proxies for the getPosMapping() of Production nodes. Retrieves the posmapping of a remote or local Production to
467 * a remote or local caller.
468 */
469 public Map<String, Integer> remotePosMapping(Address<? extends ProductionNode> production) {
470 if (!isLocal(production))
471 return production.getContainer().remotePosMapping(production);
472 return resolveLocal(production).getPosMapping();
473 }
474
475 /**
476 * Continually consumes update messages. Should be run on a dedicated thread.
477 */
478 void messageConsumptionCycle() {
479 while (!killed) // deliver messages on and on and on....
480 {
481 long incrementedClock = 0;
482 UpdateMessage message = null;
483
484 if (!internalMessageQueue.isEmpty()) // take internal messages first
485 message = internalMessageQueue.removeFirst();
486 else
487 // no internal message, take an incoming message
488 synchronized (externalMessageLock) { // no sleeping allowed,
489 // because external
490 // queue is locked for
491 // precise clocking of
492 // termination point!
493 if (!externalMessageQueue.isEmpty()) { // if external queue
494 // is non-empty,
495 // retrieve the next
496 // message instantly
497 message = takeExternalMessage();
498 } else { // if external queue is found empty (and this is
499 // the first time in a row)
500 incrementedClock = ++clock; // local termination point
501 // synchronized(clock){incrementedClock = ++clock;}
502 }
503 }
504
505 if (message == null) // both queues were empty
506 {
507 localUpdateTermination(incrementedClock); // report local
508 // termination point
509 while (message == null) // wait for a message while external
510 // queue is still empty
511 {
512 synchronized (externalMessageLock) {
513 while (externalMessageQueue.isEmpty()) {
514 try {
515 externalMessageLock.wait();
516 } catch (InterruptedException e) {
517 if (killed)
518 return;
519 }
520 }
521 message = takeExternalMessage();
522 }
523
524 }
525 }
526
527 // now we have a message to deliver
528 // NOTE: this method is not compatible with differential dataflow
529 message.receiver.update(message.direction, message.updateElement, Timestamp.ZERO);
530 }
531 }
532
533 /**
534 * @since 1.6
535 */
536 public static final Function<Node, String> NAME_MAPPER = input -> input.toString().substring(0,
537 Math.min(30, input.toString().length()));
538
539 /**
540 * Sends out all pending messages to their receivers. The delivery is governed by the communication tracker.
541 *
542 * @since 1.6
543 */
544 public void deliverMessagesSingleThreaded() {
545 if (!backendContext.areUpdatesDelayed()) {
546 if (Options.MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING) {
547 // known unreachable; enable for debugging only
548
549 CommunicationGroup lastGroup = null;
550 Set<CommunicationGroup> seenInThisCycle = new HashSet<>();
551
552 while (!tracker.isEmpty()) {
553 final CommunicationGroup group = tracker.getAndRemoveFirstGroup();
554
555 /**
556 * The current group does not violate the communication schema iff (1) it was not seen before OR (2)
557 * the last one that was seen is exactly the same as the current one this can happen if the group
558 * was added back because of in-group message passing
559 */
560 boolean okGroup = (group == lastGroup) || seenInThisCycle.add(group);
561
562 if (!okGroup) {
563 logger.error(
564 "[INTERNAL ERROR] Violation of communication schema! The communication component with representative "
565 + group.getRepresentative() + " has already been processed!");
566 }
567
568 group.deliverMessages();
569
570 lastGroup = group;
571 }
572
573 } else {
574 while (!tracker.isEmpty()) {
575 final CommunicationGroup group = tracker.getAndRemoveFirstGroup();
576 group.deliverMessages();
577 }
578 }
579 }
580 }
581
582 private void localUpdateTermination(long incrementedClock) {
583 network.reportLocalUpdateTermination(this, incrementedClock, terminationCriteria);
584 terminationCriteria.clear();
585
586 // synchronized(clock){++clock;} // +1 incrementing for parity and easy
587 // comparison
588 }
589
590 // @pre: externalMessageQueue synchronized && nonempty
591 private UpdateMessage takeExternalMessage() {
592 UpdateMessage message = externalMessageQueue.removeFirst();
593 if (!externalMessageQueue.isEmpty()) { // copy the whole queue over
594 // for speedup
595 Deque<UpdateMessage> temp = externalMessageQueue;
596 externalMessageQueue = internalMessageQueue;
597 internalMessageQueue = temp;
598 }
599 return message;
600 }
601
602 /**
603 * Provides an external address for the selected node.
604 *
605 * @pre node belongs to this container.
606 */
607 public <N extends Node> Address<N> makeAddress(N node) {
608 return new Address<N>(node);
609 }
610
611 /**
612 * Checks whether a certain address points to a node at this container.
613 */
614 public boolean isLocal(Address<? extends Node> address) {
615 return address.getContainer() == this;
616 }
617
618 /**
619 * Returns an addressed node at this container.
620 *
621 * @pre: address.container == this, e.g. address MUST be local
622 * @throws IllegalArgumentException
623 * if address is non-local
624 */
625 @SuppressWarnings("unchecked")
626 public <N extends Node> N resolveLocal(Address<N> address) {
627 if (this != address.getContainer())
628 throw new IllegalArgumentException(String.format("Address %s non-local at container %s", address, this));
629
630 N cached = address.getNodeCache();
631 if (cached != null)
632 return cached;
633 else {
634 N node = (N) nodesById.get(address.getNodeId());
635 address.setNodeCache(node);
636 return node;
637 }
638 }
639
640 /**
641 * Registers a node into the rete network (should be called by constructor). Every node MUST be registered by its
642 * constructor.
643 *
644 * @return the unique nodeId issued to the node.
645 */
646 public long registerNode(Node n) {
647 long id = nextId++;
648 nodesById.put(id, n);
649 return id;
650 }
651
652 /**
653 * Unregisters a node from the rete network. Do NOT call if node is still connected to other Nodes, or Adressed or
654 * otherwise referenced.
655 */
656 public void unregisterNode(Node n) {
657 nodesById.remove(n.getNodeId());
658 }
659
660 /**
661 * Registers a pattern memory into the rete network. Every memory MUST be registered by its owner node.
662 */
663 public void registerClearable(Clearable c) {
664 clearables.addFirst(c);
665 }
666
667 /**
668 * Unregisters a pattern memory from the rete network.
669 */
670 public void unregisterClearable(Clearable c) {
671 clearables.remove(c);
672 }
673
674 /**
675 * Clears all memory contents in the network. Reverts to initial state.
676 */
677 public void clearAll() {
678 for (Clearable c : clearables) {
679 c.clear();
680 }
681 }
682
683 public NodeFactory getNodeFactory() {
684 return network.getNodeFactory();
685 }
686
687 public ConnectionFactory getConnectionFactory() {
688 return connectionFactory;
689 }
690
691 public NodeProvisioner getProvisioner() {
692 return nodeProvisioner;
693 }
694
695 public Network getNetwork() {
696 return network;
697 }
698
699 @Override
700 public String toString() {
701 StringBuilder sb = new StringBuilder();
702 String separator = System.getProperty("line.separator");
703 sb.append(super.toString() + "[[[" + separator);
704 java.util.List<Long> keys = new java.util.ArrayList<Long>(nodesById.keySet());
705 java.util.Collections.sort(keys);
706 for (Long key : keys) {
707 sb.append(key + " -> " + nodesById.get(key) + separator);
708 }
709 sb.append("]]] of " + network);
710 return sb.toString();
711 }
712
713 /**
714 * Access all the Rete nodes inside this container.
715 *
716 * @return the collection of {@link Node} instances
717 */
718 public Collection<Node> getAllNodes() {
719 return nodesById.values();
720 }
721
722 public InputConnector getInputConnectionFactory() {
723 return network.getInputConnector();
724 }
725
726 public void checkCancelled() {
727 cancellationToken.checkCancelled();
728 }
729}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java
new file mode 100644
index 00000000..7dc7c4bc
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java
@@ -0,0 +1,123 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10
11package tools.refinery.viatra.runtime.rete.network;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
15import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.rete.index.GenericProjectionIndexer;
18import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
21import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
22
23import java.util.Collection;
24import java.util.HashSet;
25import java.util.List;
26import java.util.Set;
27
28/**
29 * Base implementation for a supplier node.
30 *
31 * @author Gabor Bergmann
32 *
33 */
34public abstract class StandardNode extends BaseNode implements Supplier, NetworkStructureChangeSensitiveNode {
35 protected final List<Receiver> children = CollectionsFactory.createObserverList();
36 /**
37 * @since 2.2
38 */
39 protected final List<Mailbox> childMailboxes = CollectionsFactory.createObserverList();
40
41 public StandardNode(final ReteContainer reteContainer) {
42 super(reteContainer);
43 }
44
45 /**
46 * @since 2.4
47 */
48 protected void propagateUpdate(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
49 reteContainer.checkCancelled();
50 for (final Mailbox childMailbox : childMailboxes) {
51 childMailbox.postMessage(direction, updateElement, timestamp);
52 }
53 }
54
55 @Override
56 public void appendChild(final Receiver receiver) {
57 children.add(receiver);
58 childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox()));
59 }
60
61 @Override
62 public void removeChild(final Receiver receiver) {
63 children.remove(receiver);
64 Mailbox mailboxToRemove = null;
65 for (final Mailbox mailbox : childMailboxes) {
66 if (mailbox.getReceiver() == receiver) {
67 mailboxToRemove = mailbox;
68 break;
69 }
70 }
71 assert mailboxToRemove != null;
72 childMailboxes.remove(mailboxToRemove);
73 }
74
75 @Override
76 public void networkStructureChanged() {
77 childMailboxes.clear();
78 for (final Receiver receiver : children) {
79 childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox()));
80 }
81 }
82
83 @Override
84 public Collection<Receiver> getReceivers() {
85 return children;
86 }
87
88 /**
89 * @since 2.2
90 */
91 public Collection<Mailbox> getChildMailboxes() {
92 return this.childMailboxes;
93 }
94
95 @Override
96 public Set<Tuple> getPulledContents(final boolean flush) {
97 final HashSet<Tuple> results = new HashSet<Tuple>();
98 pullInto(results, flush);
99 return results;
100 }
101
102 @Override
103 public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) {
104 final GenericProjectionIndexer indexer = new GenericProjectionIndexer(reteContainer, mask);
105 for (final TraceInfo traceInfo : traces) {
106 indexer.assignTraceInfo(traceInfo);
107 }
108 reteContainer.connectAndSynchronize(this, indexer);
109 return indexer;
110 }
111
112 /**
113 * @since 1.6
114 */
115 protected void issueError(final String message, final Exception ex) {
116 if (ex == null) {
117 this.reteContainer.getNetwork().getEngine().getLogger().error(message);
118 } else {
119 this.reteContainer.getNetwork().getEngine().getLogger().error(message, ex);
120 }
121 }
122
123}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java
new file mode 100644
index 00000000..1917a7cf
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21import tools.refinery.viatra.runtime.rete.single.TrimmerNode;
22import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
23
24/**
25 * @author Gabor Bergmann
26 *
27 * A supplier is an object that can propagate insert or revoke events towards receivers.
28 */
29public interface Supplier extends Node {
30
31 /**
32 * Pulls the contents of this object in this particular moment into a target collection.
33 *
34 * @param flush if true, flushing of messages is allowed during the pull, otherwise flushing is not allowed
35 * @since 2.3
36 */
37 public void pullInto(Collection<Tuple> collector, boolean flush);
38
39 /**
40 * @since 2.4
41 */
42 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush);
43
44 /**
45 * Returns the contents of this object in this particular moment.
46 * For memoryless nodes, this may involve a costly recomputation of contents.
47 *
48 * The result is returned as a Set, even when it has multiplicities (at the output of {@link TrimmerNode}).
49 *
50 * <p> Intended mainly for debug purposes; therefore flushing is performed only if explicitly requested
51 * During runtime, flushing may be preferred; see {@link ReteContainer#pullContents(Supplier)}
52 * @since 2.3
53 */
54 public Set<Tuple> getPulledContents(boolean flush);
55
56 default public Set<Tuple> getPulledContents() {
57 return getPulledContents(true);
58 }
59
60 /**
61 * appends a receiver that will continously receive insert and revoke updates from this supplier
62 */
63 void appendChild(Receiver receiver);
64
65 /**
66 * removes a receiver
67 */
68 void removeChild(Receiver receiver);
69
70 /**
71 * Instantiates (or reuses, depending on implementation) an index according to the given mask.
72 *
73 * Intended for internal use; clients should invoke through Library instead to enable reusing.
74 */
75 ProjectionIndexer constructIndex(TupleMask mask, TraceInfo... traces);
76
77 /**
78 * lists receivers
79 */
80 Collection<Receiver> getReceivers();
81
82}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java
new file mode 100644
index 00000000..f238f47b
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java
@@ -0,0 +1,19 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12/**
13 * @author Gabor Bergmann
14 *
15 * A Tunnel is an interface into which elments can be instered and from which productions can be extracted.
16 */
17public interface Tunnel extends Supplier, Receiver {
18
19}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java
new file mode 100644
index 00000000..1334a3a9
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.network;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.util.Direction;
14
15class UpdateMessage {
16 public Receiver receiver;
17 public Direction direction;
18 public Tuple updateElement;
19
20 public UpdateMessage(Receiver receiver, Direction direction, Tuple updateElement) {
21 this.receiver = receiver;
22 this.direction = direction;
23 this.updateElement = updateElement;
24 }
25
26 @Override
27 public String toString() {
28 return "M." + direction + ": " + updateElement + " -> " + receiver;
29 }
30
31}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java
new file mode 100644
index 00000000..8cedeb11
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java
@@ -0,0 +1,103 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11import java.util.Collection;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.rete.network.Node;
15import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
16
17/**
18 * A communication group represents a set of nodes in the communication graph that form a strongly connected component.
19 *
20 * @author Tamas Szabo
21 * @since 1.6
22 */
23public abstract class CommunicationGroup implements Comparable<CommunicationGroup> {
24
25 public static final String UNSUPPORTED_MESSAGE_KIND = "Unsupported message kind ";
26
27 /**
28 * Marker for the {@link CommunicationTracker}
29 */
30 public boolean isEnqueued = false;
31
32 protected final Node representative;
33
34 /**
35 * May be changed during bumping in {@link CommunicationTracker.registerDependency}
36 */
37 protected int identifier;
38
39 /**
40 * @since 1.7
41 */
42 protected final CommunicationTracker tracker;
43
44 /**
45 * @since 1.7
46 */
47 public CommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) {
48 this.tracker = tracker;
49 this.representative = representative;
50 this.identifier = identifier;
51 }
52
53 public abstract void deliverMessages();
54
55 public Node getRepresentative() {
56 return representative;
57 }
58
59 public abstract boolean isEmpty();
60
61 /**
62 * @since 2.0
63 */
64 public abstract void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind);
65
66 /**
67 * @since 2.0
68 */
69 public abstract void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind);
70
71 public abstract Map<MessageSelector, Collection<Mailbox>> getMailboxes();
72
73 public abstract boolean isRecursive();
74
75 @Override
76 public int hashCode() {
77 return this.identifier;
78 }
79
80 @Override
81 public String toString() {
82 return this.getClass().getSimpleName() + " " + this.identifier + " - representative: " + this.representative
83 + " - isEmpty: " + isEmpty();
84 }
85
86 @Override
87 public boolean equals(final Object obj) {
88 if (obj == null || this.getClass() != obj.getClass()) {
89 return false;
90 } else if (this == obj) {
91 return true;
92 } else {
93 final CommunicationGroup that = (CommunicationGroup) obj;
94 return this.identifier == that.identifier;
95 }
96 }
97
98 @Override
99 public int compareTo(final CommunicationGroup that) {
100 return this.identifier - that.identifier;
101 }
102
103}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java
new file mode 100644
index 00000000..d244e644
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java
@@ -0,0 +1,467 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.PriorityQueue;
16import java.util.Queue;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.rete.itc.alg.incscc.IncSCCAlg;
20import tools.refinery.viatra.runtime.rete.itc.alg.misc.topsort.TopologicalSorting;
21import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
22import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
23import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode;
24import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode;
25import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode;
26import tools.refinery.viatra.runtime.rete.index.DualInputNode;
27import tools.refinery.viatra.runtime.rete.index.ExistenceNode;
28import tools.refinery.viatra.runtime.rete.index.Indexer;
29import tools.refinery.viatra.runtime.rete.index.IndexerListener;
30import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
31import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer;
32import tools.refinery.viatra.runtime.rete.network.IGroupable;
33import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
34import tools.refinery.viatra.runtime.rete.network.Node;
35import tools.refinery.viatra.runtime.rete.network.ProductionNode;
36import tools.refinery.viatra.runtime.rete.network.Receiver;
37import tools.refinery.viatra.runtime.rete.network.ReteContainer;
38import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyIndexerListenerProxy;
39import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy;
40import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox;
41import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
42import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
43import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode;
44import tools.refinery.viatra.runtime.rete.single.TrimmerNode;
45
46/**
47 * An instance of this class is associated with every {@link ReteContainer}. The tracker serves two purposes: <br>
48 * (1) It allows RETE nodes to register their communication dependencies on-the-fly. These dependencies can be
49 * registered or unregistered when nodes are disposed of. <br>
50 * (2) It allows RETE nodes to register their mailboxes as dirty, that is, they can tell the tracker that they have
51 * something to send to other nodes in the network. The tracker is then responsible for ordering these messages (more
52 * precisely, the mailboxes that contain the messages) for the associated {@link ReteContainer}. The ordering is
53 * governed by the strongly connected components in the dependency network and follows a topological sorting scheme;
54 * those mailboxes will be emptied first whose owner nodes do not depend on other undelivered messages.
55 *
56 * @author Tamas Szabo
57 * @since 1.6
58 *
59 */
60public abstract class CommunicationTracker {
61
62 /**
63 * The minimum group id assigned so far
64 */
65 protected int minGroupId;
66
67 /**
68 * The maximum group id assigned so far
69 */
70 protected int maxGroupId;
71
72 /**
73 * The dependency graph of the communications in the RETE network
74 */
75 protected final Graph<Node> dependencyGraph;
76
77 /**
78 * Incremental SCC information about the dependency graph
79 */
80 protected final IncSCCAlg<Node> sccInformationProvider;
81
82 /**
83 * Precomputed node -> communication group map
84 */
85 protected final Map<Node, CommunicationGroup> groupMap;
86
87 /**
88 * Priority queue of active communication groups
89 */
90 protected final Queue<CommunicationGroup> groupQueue;
91
92 // groups should have a simple integer flag which represents its position in a priority queue
93 // priority queue only contains the ACTIVE groups
94
95 public CommunicationTracker() {
96 this.dependencyGraph = new Graph<Node>();
97 this.sccInformationProvider = new IncSCCAlg<Node>(this.dependencyGraph);
98 this.groupQueue = new PriorityQueue<CommunicationGroup>();
99 this.groupMap = new HashMap<Node, CommunicationGroup>();
100 }
101
102 public Graph<Node> getDependencyGraph() {
103 return dependencyGraph;
104 }
105
106 public CommunicationGroup getGroup(final Node node) {
107 return this.groupMap.get(node);
108 }
109
110 private void precomputeGroups() {
111 groupMap.clear();
112
113 // reconstruct group map from dependency graph
114 final Graph<Node> reducedGraph = sccInformationProvider.getReducedGraph();
115 final List<Node> representatives = TopologicalSorting.compute(reducedGraph);
116
117 for (int i = 0; i < representatives.size(); i++) { // groups for SCC representatives
118 final Node representative = representatives.get(i);
119 createAndStoreGroup(representative, i);
120 }
121
122 minGroupId = 0;
123 maxGroupId = representatives.size() - 1;
124
125 for (final Node node : dependencyGraph.getAllNodes()) { // extend group map to the rest of nodes
126 final Node representative = sccInformationProvider.getRepresentative(node);
127 final CommunicationGroup group = groupMap.get(representative);
128 if (representative != node) {
129 addToGroup(node, group);
130 }
131 }
132
133 for (final Node node : dependencyGraph.getAllNodes()) {
134 // set fall-through flags of default mailboxes
135 precomputeFallThroughFlag(node);
136 // perform further tracker-specific post-processing
137 postProcessNode(node);
138 }
139
140 // reconstruct new queue contents based on new group map
141 if (!groupQueue.isEmpty()) {
142 final Set<CommunicationGroup> oldActiveGroups = new HashSet<CommunicationGroup>(groupQueue);
143 groupQueue.clear();
144 reconstructQueueContents(oldActiveGroups);
145 }
146
147 // post process the groups
148 for (final CommunicationGroup group : groupMap.values()) {
149 postProcessGroup(group);
150 }
151 }
152
153 /**
154 * This method is responsible for reconstructing the active queue contents after the network structure has changed.
155 * It it defined as abstract because the reconstruction logic is specific to each {@link CommunicationTracker}.
156 * @since 2.4
157 */
158 protected abstract void reconstructQueueContents(final Set<CommunicationGroup> oldActiveGroups);
159
160 private void addToGroup(final Node node, final CommunicationGroup group) {
161 groupMap.put(node, group);
162 if (node instanceof Receiver) {
163 ((Receiver) node).getMailbox().setCurrentGroup(group);
164 if (node instanceof IGroupable) {
165 ((IGroupable) node).setCurrentGroup(group);
166 }
167 }
168 }
169
170 /**
171 * Depends on the groups, as well as the parent nodes of the argument, so recomputation is needed if these change
172 */
173 private void precomputeFallThroughFlag(final Node node) {
174 CommunicationGroup group = groupMap.get(node);
175 if (node instanceof Receiver) {
176 IGroupable mailbox = ((Receiver) node).getMailbox();
177 if (mailbox instanceof FallThroughCapableMailbox) {
178 Set<Node> directParents = dependencyGraph.getSourceNodes(node).distinctValues();
179 // decide between using quick&cheap fall-through, or allowing for update cancellation
180 boolean fallThrough =
181 // disallow fallthrough: updates at production nodes should cancel, if they can be trimmed or
182 // disjunctive
183 (!(node instanceof ProductionNode && ( // it is a production node...
184 // with more than one parent
185 directParents.size() > 0 ||
186 // or true trimming in its sole parent
187 directParents.size() == 1 && trueTrimming(directParents.iterator().next())))) &&
188 // disallow fallthrough: external updates should be stored (if updates are delayed)
189 (!(node instanceof ExternalInputEnumeratorNode)) &&
190 // disallow fallthrough: RelationEvaluatorNode needs to be notified in batch-style, and the batching is done by the mailbox
191 // however, it is not the RelationEvaluatorNode itself that is interesting here, as that indirectly uses the BatchingReceiver
192 // so we need to disable fall-through for the BatchingReceiver
193 (!(node instanceof RelationEvaluatorNode.BatchingReceiver));
194 // do additional checks
195 if (fallThrough) {
196 // recursive parent groups generate excess updates that should be cancelled after delete&rederive
197 // phases
198 // aggregator and transitive closure parent nodes also generate excess updates that should be
199 // cancelled
200 directParentLoop: for (Node directParent : directParents) {
201 Set<Node> parentsToCheck = new HashSet<>();
202 // check the case where a direct parent is the reason for mailbox usage
203 parentsToCheck.add(directParent);
204 // check the case where an indirect parent (join slot) is the reason for mailbox usage
205 if (directParent instanceof DualInputNode) {
206 // in case of existence join (typically antijoin), a mailbox should allow
207 // an insertion and deletion (at the secondary slot) to cancel each other out
208 if (directParent instanceof ExistenceNode) {
209 fallThrough = false;
210 break directParentLoop;
211 }
212 // in beta nodes, indexer slots (or their active nodes) are considered indirect parents
213 DualInputNode dualInput = (DualInputNode) directParent;
214 IterableIndexer primarySlot = dualInput.getPrimarySlot();
215 if (primarySlot != null)
216 parentsToCheck.add(primarySlot.getActiveNode());
217 Indexer secondarySlot = dualInput.getSecondarySlot();
218 if (secondarySlot != null)
219 parentsToCheck.add(secondarySlot.getActiveNode());
220 }
221 for (Node parent : parentsToCheck) {
222 CommunicationGroup parentGroup = groupMap.get(parent);
223 if ( // parent is in a different, recursive group
224 (group != parentGroup && parentGroup.isRecursive()) ||
225 // node and parent within the same recursive group, and...
226 (group == parentGroup && group.isRecursive() && (
227 // parent is a transitive closure or aggregator node, or a trimmer
228 // allow trimmed or disjunctive tuple updates to cancel each other
229 (parent instanceof TransitiveClosureNode) || (parent instanceof IAggregatorNode)
230 || trueTrimming(parent)))) {
231 fallThrough = false;
232 break directParentLoop;
233 }
234 }
235 }
236 }
237 // overwrite fallthrough flag with newly computed value
238 ((FallThroughCapableMailbox) mailbox).setFallThrough(fallThrough);
239 }
240 }
241 }
242
243 /**
244 * A trimmer node that actually eliminates some columns (not just reorders)
245 */
246 private boolean trueTrimming(Node node) {
247 if (node instanceof TrimmerNode) {
248 TupleMask mask = ((TrimmerNode) node).getMask();
249 return (mask.indices.length != mask.sourceWidth);
250 }
251 return false;
252 }
253
254 public void activateUnenqueued(final CommunicationGroup group) {
255 groupQueue.add(group);
256 group.isEnqueued = true;
257 }
258
259 public void deactivate(final CommunicationGroup group) {
260 groupQueue.remove(group);
261 group.isEnqueued = false;
262 }
263
264 public CommunicationGroup getAndRemoveFirstGroup() {
265 final CommunicationGroup group = groupQueue.poll();
266 group.isEnqueued = false;
267 return group;
268 }
269
270 public boolean isEmpty() {
271 return groupQueue.isEmpty();
272 }
273
274 protected abstract CommunicationGroup createGroup(final Node representative, final int index);
275
276 protected CommunicationGroup createAndStoreGroup(final Node representative, final int index) {
277 final CommunicationGroup group = createGroup(representative, index);
278 addToGroup(representative, group);
279 return group;
280 }
281
282 /**
283 * Registers the dependency that the target {@link Node} depends on the source {@link Node}. In other words, source
284 * may send messages to target in the RETE network. If the dependency edge is already present, this method call is a
285 * noop.
286 *
287 * @param source
288 * the source node
289 * @param target
290 * the target node
291 */
292 public void registerDependency(final Node source, final Node target) {
293 // nodes can be immediately inserted, if they already exist in the graph, this is a noop
294 dependencyGraph.insertNode(source);
295 dependencyGraph.insertNode(target);
296
297 if (!this.dependencyGraph.getTargetNodes(source).containsNonZero(target)) {
298
299 // query all these information before the actual edge insertion
300 // because SCCs may be unified during the process
301 final Node sourceRepresentative = sccInformationProvider.getRepresentative(source);
302 final Node targetRepresentative = sccInformationProvider.getRepresentative(target);
303 final boolean targetHadOutgoingEdges = sccInformationProvider.hasOutgoingEdges(targetRepresentative);
304
305 // insert the edge
306 dependencyGraph.insertEdge(source, target);
307
308 // create groups if they do not yet exist
309 CommunicationGroup sourceGroup = groupMap.get(sourceRepresentative);
310 if (sourceGroup == null) {
311 // create on-demand with the next smaller group id
312 sourceGroup = createAndStoreGroup(sourceRepresentative, --minGroupId);
313 }
314 final int sourceIndex = sourceGroup.identifier;
315
316 CommunicationGroup targetGroup = groupMap.get(targetRepresentative);
317 if (targetGroup == null) {
318 // create on-demand with the next larger group id
319 targetGroup = createAndStoreGroup(targetRepresentative, ++maxGroupId);
320 }
321 final int targetIndex = targetGroup.identifier;
322
323 if (sourceIndex <= targetIndex) {
324 // indices obey current topological ordering
325 refreshFallThroughFlag(target);
326 postProcessNode(source);
327 postProcessNode(target);
328 postProcessGroup(sourceGroup);
329 if (sourceGroup != targetGroup) {
330 postProcessGroup(targetGroup);
331 }
332 } else if (sourceIndex > targetIndex && !targetHadOutgoingEdges) {
333 // indices violate current topological ordering, but we can simply bump the target index
334 final boolean wasEnqueued = targetGroup.isEnqueued;
335 if (wasEnqueued) {
336 groupQueue.remove(targetGroup);
337 }
338 targetGroup.identifier = ++maxGroupId;
339 if (wasEnqueued) {
340 groupQueue.add(targetGroup);
341 }
342
343 refreshFallThroughFlag(target);
344 postProcessNode(source);
345 postProcessNode(target);
346 postProcessGroup(sourceGroup);
347 postProcessGroup(targetGroup);
348 } else {
349 // needs a full re-computation because of more complex change
350 precomputeGroups();
351 }
352 }
353 }
354
355 /**
356 * Returns true if the given {@link Node} is in a recursive {@link CommunicationGroup}, false otherwise.
357 */
358 public boolean isInRecursiveGroup(final Node node) {
359 final CommunicationGroup group = this.getGroup(node);
360 if (group == null) {
361 return false;
362 } else {
363 return group.isRecursive();
364 }
365 }
366
367 /**
368 * Returns true if the given two {@link Node}s are in the same {@link CommunicationGroup}.
369 */
370 public boolean areInSameGroup(final Node left, final Node right) {
371 final CommunicationGroup leftGroup = this.getGroup(left);
372 final CommunicationGroup rightGroup = this.getGroup(right);
373 return leftGroup != null && leftGroup == rightGroup;
374 }
375
376 /**
377 * Unregisters a dependency between source and target.
378 *
379 * @param source
380 * the source node
381 * @param target
382 * the target node
383 */
384 public void unregisterDependency(final Node source, final Node target) {
385 // delete the edge first, and then query the SCC info provider
386 this.dependencyGraph.deleteEdgeIfExists(source, target);
387
388 final Node sourceRepresentative = sccInformationProvider.getRepresentative(source);
389 final Node targetRepresentative = sccInformationProvider.getRepresentative(target);
390
391 // if they are still in the same SCC,
392 // then this deletion did not affect the SCCs,
393 // and it is sufficient to recompute affected fall-through flags;
394 // otherwise, we need a new pre-computation for the groupMap and groupQueue
395 if (sourceRepresentative.equals(targetRepresentative)) {
396 // this deletion could not have affected the split flags
397 refreshFallThroughFlag(target);
398 postProcessNode(source);
399 postProcessNode(target);
400 } else {
401 // preComputeGroups takes care of the split flag maintenance
402 precomputeGroups();
403 }
404 }
405
406 /**
407 * Refresh fall-through flags if dependencies change for given target, but no SCC change
408 */
409 private void refreshFallThroughFlag(final Node target) {
410 precomputeFallThroughFlag(target);
411 if (target instanceof DualInputNode) {
412 for (final Node indirectTarget : dependencyGraph.getTargetNodes(target).distinctValues()) {
413 precomputeFallThroughFlag(indirectTarget);
414 }
415 }
416 }
417
418 /**
419 * Returns true if the given source-target edge in the communication network acts as a recursion cut point.
420 * The current implementation considers edges leading into {@link ProductionNode}s as cut point iff
421 * both source and target belong to the same group.
422 *
423 * @param source the source node
424 * @param target the target node
425 * @return true if the edge is a cut point, false otherwise
426 * @since 2.4
427 */
428 protected boolean isRecursionCutPoint(final Node source, final Node target) {
429 final Node effectiveSource = source instanceof SpecializedProjectionIndexer
430 ? ((SpecializedProjectionIndexer) source).getActiveNode()
431 : source;
432 final CommunicationGroup sourceGroup = this.getGroup(effectiveSource);
433 final CommunicationGroup targetGroup = this.getGroup(target);
434 return sourceGroup != null && sourceGroup == targetGroup && target instanceof ProductionNode;
435 }
436
437 /**
438 * This hook allows concrete tracker implementations to perform tracker-specific post processing on nodes (cf.
439 * {@link NetworkStructureChangeSensitiveNode} and {@link BehaviorChangingMailbox}). At the time of the invocation,
440 * the network topology has already been updated.
441 */
442 protected abstract void postProcessNode(final Node node);
443
444 /**
445 * This hook allows concrete tracker implementations to perform tracker-specific post processing on groups. At the
446 * time of the invocation, the network topology has already been updated.
447 * @since 2.4
448 */
449 protected abstract void postProcessGroup(final CommunicationGroup group);
450
451 /**
452 * Creates a proxy for the given {@link Mailbox} for the given requester {@link Node}. The proxy creation is
453 * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used
454 * to create {@link TimelyMailboxProxy}s depending on the network topology. There is no guarantee that the same
455 * proxy instance is returned when this method is called multiple times with the same arguments.
456 */
457 public abstract Mailbox proxifyMailbox(final Node requester, final Mailbox original);
458
459 /**
460 * Creates a proxy for the given {@link IndexerListener} for the given requester {@link Node}. The proxy creation is
461 * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used
462 * to create {@link TimelyIndexerListenerProxy}s depending on the network topology. There is no guarantee that the
463 * same proxy instance is returned when this method is called multiple times with the same arguments.
464 */
465 public abstract IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original);
466
467}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java
new file mode 100644
index 00000000..e1a61693
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java
@@ -0,0 +1,19 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11/**
12 * Subclasses of this interface represent meta data of update messages in Rete.
13 *
14 * @author Tamas Szabo
15 * @since 2.3
16 */
17public interface MessageSelector {
18
19}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java
new file mode 100644
index 00000000..27779352
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11import java.util.Comparator;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.rete.network.Node;
15
16/**
17 * @since 2.4
18 */
19public class NodeComparator implements Comparator<Node> {
20
21 protected final Map<Node, Integer> nodeMap;
22
23 public NodeComparator(final Map<Node, Integer> nodeMap) {
24 this.nodeMap = nodeMap;
25 }
26
27 @Override
28 public int compare(final Node left, final Node right) {
29 return this.nodeMap.get(left) - this.nodeMap.get(right);
30 }
31
32} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java
new file mode 100644
index 00000000..41cd8cd3
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11/**
12 * A default message selector that can be used to associate phases to messages.
13 *
14 * @author Tamas Szabo
15 * @since 2.3
16 */
17public enum PhasedSelector implements MessageSelector {
18
19 /**
20 * No special distinguishing feature
21 */
22 DEFAULT,
23
24 /**
25 * Inserts and delete-insert monotone change pairs
26 */
27 MONOTONE,
28
29 /**
30 * Deletes
31 */
32 ANTI_MONOTONE
33
34}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java
new file mode 100644
index 00000000..a50a63a8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java
@@ -0,0 +1,124 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication;
10
11import java.util.AbstractMap;
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
18
19/**
20 * A timestamp associated with update messages in timely evaluation.
21 *
22 * @author Tamas Szabo
23 * @since 2.3
24 */
25public class Timestamp implements Comparable<Timestamp>, MessageSelector {
26
27 protected final int value;
28 public static final Timestamp ZERO = new Timestamp(0);
29 /**
30 * @since 2.4
31 */
32 public static final Timeline<Timestamp> INSERT_AT_ZERO_TIMELINE = Timelines.createFrom(Timestamp.ZERO);
33
34 public Timestamp(final int value) {
35 this.value = value;
36 }
37
38 public int getValue() {
39 return value;
40 }
41
42 public Timestamp max(final Timestamp that) {
43 if (this.value >= that.value) {
44 return this;
45 } else {
46 return that;
47 }
48 }
49
50 /**
51 * @since 2.4
52 */
53 public Timestamp min(final Timestamp that) {
54 if (this.value <= that.value) {
55 return this;
56 } else {
57 return that;
58 }
59 }
60
61 @Override
62 public int compareTo(final Timestamp that) {
63 return this.value - that.value;
64 }
65
66 @Override
67 public boolean equals(final Object obj) {
68 if (obj == null || !(obj instanceof Timestamp)) {
69 return false;
70 } else {
71 return this.value == ((Timestamp) obj).value;
72 }
73 }
74
75 @Override
76 public int hashCode() {
77 return this.value;
78 }
79
80 @Override
81 public String toString() {
82 return Integer.toString(this.value);
83 }
84
85 /**
86 * A {@link Map} implementation that associates the zero timestamp with every key. There is no suppor for
87 * {@link Map#entrySet()} due to performance reasons.
88 *
89 * @author Tamas Szabo
90 */
91 public static final class AllZeroMap<T> extends AbstractMap<T, Timeline<Timestamp>> {
92
93 private final Collection<T> wrapped;
94
95 public AllZeroMap(Set<T> wrapped) {
96 this.wrapped = wrapped;
97 }
98
99 @Override
100 public Set<Entry<T, Timeline<Timestamp>>> entrySet() {
101 throw new UnsupportedOperationException("Use the combination of keySet() and get()!");
102 }
103
104 /**
105 * @since 2.4
106 */
107 @Override
108 public Timeline<Timestamp> get(final Object key) {
109 return INSERT_AT_ZERO_TIMELINE;
110 }
111
112 @Override
113 public Set<T> keySet() {
114 return (Set<T>) this.wrapped;
115 }
116
117 @Override
118 public String toString() {
119 return this.getClass().getSimpleName() + ": " + this.keySet().toString();
120 }
121
122 }
123
124}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java
new file mode 100644
index 00000000..d8260384
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timeless;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.EnumMap;
14import java.util.LinkedHashSet;
15import java.util.Map;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.rete.network.Node;
20import tools.refinery.viatra.runtime.rete.network.RederivableNode;
21import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
22import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
23import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
24import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector;
25import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
26
27/**
28 * A communication group representing either a single node where the
29 * node is a monotonicity aware one or a set of nodes that form an SCC.
30 *
31 * @author Tamas Szabo
32 * @since 2.4
33 */
34public class RecursiveCommunicationGroup extends CommunicationGroup {
35
36 private final Set<Mailbox> antiMonotoneMailboxes;
37 private final Set<Mailbox> monotoneMailboxes;
38 private final Set<Mailbox> defaultMailboxes;
39 private final Set<RederivableNode> rederivables;
40 private boolean currentlyDelivering;
41
42 /**
43 * @since 1.7
44 */
45 public RecursiveCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) {
46 super(tracker, representative, identifier);
47 this.antiMonotoneMailboxes = CollectionsFactory.createSet();
48 this.monotoneMailboxes = CollectionsFactory.createSet();
49 this.defaultMailboxes = CollectionsFactory.createSet();
50 this.rederivables = new LinkedHashSet<RederivableNode>();
51 this.currentlyDelivering = false;
52 }
53
54 @Override
55 public void deliverMessages() {
56 this.currentlyDelivering = true;
57
58 // ANTI-MONOTONE PHASE
59 while (!this.antiMonotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) {
60 while (!this.antiMonotoneMailboxes.isEmpty()) {
61 final Mailbox mailbox = this.antiMonotoneMailboxes.iterator().next();
62 this.antiMonotoneMailboxes.remove(mailbox);
63 mailbox.deliverAll(PhasedSelector.ANTI_MONOTONE);
64 }
65 while (!this.defaultMailboxes.isEmpty()) {
66 final Mailbox mailbox = this.defaultMailboxes.iterator().next();
67 this.defaultMailboxes.remove(mailbox);
68 mailbox.deliverAll(PhasedSelector.DEFAULT);
69 }
70 }
71
72 // REDERIVE PHASE
73 while (!this.rederivables.isEmpty()) {
74 // re-derivable nodes take care of their unregistration!!
75 final RederivableNode node = this.rederivables.iterator().next();
76 node.rederiveOne();
77 }
78
79 // MONOTONE PHASE
80 while (!this.monotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) {
81 while (!this.monotoneMailboxes.isEmpty()) {
82 final Mailbox mailbox = this.monotoneMailboxes.iterator().next();
83 this.monotoneMailboxes.remove(mailbox);
84 mailbox.deliverAll(PhasedSelector.MONOTONE);
85 }
86 while (!this.defaultMailboxes.isEmpty()) {
87 final Mailbox mailbox = this.defaultMailboxes.iterator().next();
88 this.defaultMailboxes.remove(mailbox);
89 mailbox.deliverAll(PhasedSelector.DEFAULT);
90 }
91 }
92
93 this.currentlyDelivering = false;
94 }
95
96 @Override
97 public boolean isEmpty() {
98 return this.rederivables.isEmpty() && this.antiMonotoneMailboxes.isEmpty()
99 && this.monotoneMailboxes.isEmpty() && this.defaultMailboxes.isEmpty();
100 }
101
102 @Override
103 public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) {
104 final Collection<Mailbox> mailboxes = getMailboxContainer(kind);
105 mailboxes.add(mailbox);
106 if (!this.isEnqueued && !this.currentlyDelivering) {
107 this.tracker.activateUnenqueued(this);
108 }
109 }
110
111 @Override
112 public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) {
113 final Collection<Mailbox> mailboxes = getMailboxContainer(kind);
114 mailboxes.remove(mailbox);
115 if (isEmpty()) {
116 this.tracker.deactivate(this);
117 }
118 }
119
120 private Collection<Mailbox> getMailboxContainer(final MessageSelector kind) {
121 if (kind == PhasedSelector.ANTI_MONOTONE) {
122 return this.antiMonotoneMailboxes;
123 } else if (kind == PhasedSelector.MONOTONE) {
124 return this.monotoneMailboxes;
125 } else if (kind == PhasedSelector.DEFAULT) {
126 return this.defaultMailboxes;
127 } else {
128 throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind);
129 }
130 }
131
132 public void addRederivable(final RederivableNode node) {
133 this.rederivables.add(node);
134 if (!this.isEnqueued) {
135 this.tracker.activateUnenqueued(this);
136 }
137 }
138
139 public void removeRederivable(final RederivableNode node) {
140 this.rederivables.remove(node);
141 if (isEmpty()) {
142 this.tracker.deactivate(this);
143 }
144 }
145
146 public Collection<RederivableNode> getRederivables() {
147 return this.rederivables;
148 }
149
150 @Override
151 public Map<MessageSelector, Collection<Mailbox>> getMailboxes() {
152 Map<PhasedSelector, Collection<Mailbox>> map = new EnumMap<>(PhasedSelector.class);
153 map.put(PhasedSelector.ANTI_MONOTONE, antiMonotoneMailboxes);
154 map.put(PhasedSelector.MONOTONE, monotoneMailboxes);
155 map.put(PhasedSelector.DEFAULT, defaultMailboxes);
156 return Collections.unmodifiableMap(map);
157 }
158
159 @Override
160 public boolean isRecursive() {
161 return true;
162 }
163
164}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java
new file mode 100644
index 00000000..c51c7dbf
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timeless;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.rete.network.Node;
16import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
17import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
18import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
19import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector;
20import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
21
22/**
23 * A communication group containing only a single node with a single default
24 * mailbox.
25 *
26 * @author Tamas Szabo
27 * @since 1.6
28 */
29public class SingletonCommunicationGroup extends CommunicationGroup {
30
31 private Mailbox mailbox;
32
33 /**
34 * @since 1.7
35 */
36 public SingletonCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) {
37 super(tracker, representative, identifier);
38 }
39
40 @Override
41 public void deliverMessages() {
42 this.mailbox.deliverAll(PhasedSelector.DEFAULT);
43 }
44
45 @Override
46 public boolean isEmpty() {
47 return this.mailbox == null;
48 }
49
50 @Override
51 public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) {
52 if (kind == PhasedSelector.DEFAULT) {
53 this.mailbox = mailbox;
54 if (!this.isEnqueued) {
55 this.tracker.activateUnenqueued(this);
56 }
57 } else {
58 throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind);
59 }
60 }
61
62 @Override
63 public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) {
64 if (kind == PhasedSelector.DEFAULT) {
65 this.mailbox = null;
66 this.tracker.deactivate(this);
67 } else {
68 throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind);
69 }
70 }
71
72 @Override
73 public Map<MessageSelector, Collection<Mailbox>> getMailboxes() {
74 if (mailbox != null) {
75 return Collections.singletonMap(PhasedSelector.DEFAULT, Collections.singleton(mailbox));
76 } else {
77 return Collections.emptyMap();
78 }
79 }
80
81 @Override
82 public boolean isRecursive() {
83 return false;
84 }
85
86}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java
new file mode 100644
index 00000000..1c18c1cd
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java
@@ -0,0 +1,149 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timeless;
10
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.Set;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.rete.index.DualInputNode;
17import tools.refinery.viatra.runtime.rete.index.Indexer;
18import tools.refinery.viatra.runtime.rete.index.IndexerListener;
19import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
20import tools.refinery.viatra.runtime.rete.network.Node;
21import tools.refinery.viatra.runtime.rete.network.Receiver;
22import tools.refinery.viatra.runtime.rete.network.RederivableNode;
23import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
24import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
25import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
26import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
27import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
28
29/**
30 * Timeless implementation of the communication tracker.
31 *
32 * @author Tamas Szabo
33 * @since 2.2
34 */
35public class TimelessCommunicationTracker extends CommunicationTracker {
36
37 @Override
38 protected CommunicationGroup createGroup(Node representative, int index) {
39 final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1;
40 final boolean isReceiver = representative instanceof Receiver;
41 final boolean isPosetIndifferent = isReceiver
42 && ((Receiver) representative).getMailbox() instanceof BehaviorChangingMailbox;
43 final boolean isSingletonInDRedMode = isSingleton && (representative instanceof RederivableNode)
44 && ((RederivableNode) representative).isInDRedMode();
45
46 CommunicationGroup group = null;
47 // we can only use a singleton group iff
48 // (1) the SCC has one node AND
49 // (2) either we have a poset-indifferent mailbox OR the node is not even a receiver AND
50 // (3) the node does not run in DRed mode in a singleton group
51 if (isSingleton && (isPosetIndifferent || !isReceiver) && !isSingletonInDRedMode) {
52 group = new SingletonCommunicationGroup(this, representative, index);
53 } else {
54 group = new RecursiveCommunicationGroup(this, representative, index);
55 }
56
57 return group;
58 }
59
60 @Override
61 protected void reconstructQueueContents(final Set<CommunicationGroup> oldActiveGroups) {
62 for (final CommunicationGroup oldGroup : oldActiveGroups) {
63 for (final Entry<MessageSelector, Collection<Mailbox>> entry : oldGroup.getMailboxes().entrySet()) {
64 for (final Mailbox mailbox : entry.getValue()) {
65 final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver());
66 newGroup.notifyHasMessage(mailbox, entry.getKey());
67 }
68 }
69
70 if (oldGroup instanceof RecursiveCommunicationGroup) {
71 for (final RederivableNode node : ((RecursiveCommunicationGroup) oldGroup).getRederivables()) {
72 final CommunicationGroup newGroup = this.groupMap.get(node);
73 if (!(newGroup instanceof RecursiveCommunicationGroup)) {
74 throw new IllegalStateException("The new group must also be recursive! " + newGroup);
75 }
76 ((RecursiveCommunicationGroup) newGroup).addRederivable(node);
77 }
78 }
79 }
80 }
81
82 @Override
83 public Mailbox proxifyMailbox(final Node requester, final Mailbox original) {
84 return original;
85 }
86
87 @Override
88 public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) {
89 return original;
90 }
91
92 @Override
93 protected void postProcessNode(final Node node) {
94 if (node instanceof Receiver) {
95 final Mailbox mailbox = ((Receiver) node).getMailbox();
96 if (mailbox instanceof BehaviorChangingMailbox) {
97 final CommunicationGroup group = this.groupMap.get(node);
98 final Set<Node> sccNodes = this.sccInformationProvider.sccs.getPartition(node);
99 // a default mailbox must split its messages iff
100 // (1) its receiver is in a recursive group and
101 final boolean c1 = group.isRecursive();
102 // (2) its receiver is at the SCC boundary of that group
103 final boolean c2 = isAtSCCBoundary(node);
104 // (3) its group consists of more than one node
105 final boolean c3 = sccNodes.size() > 1;
106 ((BehaviorChangingMailbox) mailbox).setSplitFlag(c1 && c2 && c3);
107 }
108 }
109 }
110
111 @Override
112 protected void postProcessGroup(final CommunicationGroup group) {
113
114 }
115
116 /**
117 * @since 2.0
118 */
119 private boolean isAtSCCBoundary(final Node node) {
120 final CommunicationGroup ownGroup = this.groupMap.get(node);
121 assert ownGroup != null;
122 for (final Node source : this.dependencyGraph.getSourceNodes(node).distinctValues()) {
123 final Set<Node> sourcesToCheck = new HashSet<Node>();
124 sourcesToCheck.add(source);
125 // DualInputNodes must be checked additionally because they do not use a mailbox directly.
126 // It can happen that their indexers actually belong to other SCCs.
127 if (source instanceof DualInputNode) {
128 final DualInputNode dualInput = (DualInputNode) source;
129 final IterableIndexer primarySlot = dualInput.getPrimarySlot();
130 if (primarySlot != null) {
131 sourcesToCheck.add(primarySlot.getActiveNode());
132 }
133 final Indexer secondarySlot = dualInput.getSecondarySlot();
134 if (secondarySlot != null) {
135 sourcesToCheck.add(secondarySlot.getActiveNode());
136 }
137 }
138 for (final Node current : sourcesToCheck) {
139 final CommunicationGroup otherGroup = this.groupMap.get(current);
140 assert otherGroup != null;
141 if (!ownGroup.equals(otherGroup)) {
142 return true;
143 }
144 }
145 }
146 return false;
147 }
148
149}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java
new file mode 100644
index 00000000..8097bd91
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import tools.refinery.viatra.runtime.rete.network.IGroupable;
12import tools.refinery.viatra.runtime.rete.network.Node;
13import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
14
15/**
16 * {@link Node}s that implement this interface can resume folding of their states when instructed during timely evaluation.
17 *
18 * @since 2.3
19 * @author Tamas Szabo
20 */
21public interface ResumableNode extends Node, IGroupable {
22
23 /**
24 * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to
25 * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more
26 * folding to do towards higher timestamps.
27 */
28 public void resumeAt(final Timestamp timestamp);
29
30 /**
31 * Returns the smallest timestamp where lazy folding shall be resumed, or null if there is no more folding to do in this
32 * resumable.
33 */
34 public Timestamp getResumableTimestamp();
35
36}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java
new file mode 100644
index 00000000..0394d92c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Comparator;
14import java.util.HashMap;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.Set;
18import java.util.TreeMap;
19import java.util.TreeSet;
20
21import org.apache.log4j.Logger;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.rete.network.Node;
24import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
25import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
26import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
27import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
28import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
29import tools.refinery.viatra.runtime.rete.util.Options;
30
31/**
32 * A timely communication group implementation. {@link TimelyMailbox}es and {@link LazyFoldingNode}s are ordered in the
33 * increasing order of timestamps.
34 *
35 * @author Tamas Szabo
36 * @since 2.3
37 */
38public class TimelyCommunicationGroup extends CommunicationGroup {
39
40 private final boolean isSingleton;
41 private final TreeMap<Timestamp, Set<Mailbox>> mailboxQueue;
42 // may be null - only used in the scattered case where we need to take care of mailboxes and resumables too
43 private Comparator<Node> nodeComparator;
44 private boolean currentlyDelivering;
45 private Timestamp currentlyDeliveredTimestamp;
46
47 public TimelyCommunicationGroup(final TimelyCommunicationTracker tracker, final Node representative,
48 final int identifier, final boolean isSingleton) {
49 super(tracker, representative, identifier);
50 this.isSingleton = isSingleton;
51 this.mailboxQueue = CollectionsFactory.createTreeMap();
52 this.currentlyDelivering = false;
53 }
54
55 /**
56 * Sets the {@link Comparator} to be used to order the {@link Mailbox}es at a given {@link Timestamp} in the mailbox
57 * queue. Additionally, reorders already queued {@link Mailbox}es to reflect the new comparator. The comparator may
58 * be null, in this case, no set ordering will be enforced among the {@link Mailbox}es.
59 */
60 public void setComparatorAndReorderMailboxes(final Comparator<Node> nodeComparator) {
61 this.nodeComparator = nodeComparator;
62 if (!this.mailboxQueue.isEmpty()) {
63 final HashMap<Timestamp, Set<Mailbox>> queueCopy = new HashMap<Timestamp, Set<Mailbox>>(this.mailboxQueue);
64 this.mailboxQueue.clear();
65 for (final Entry<Timestamp, Set<Mailbox>> entry : queueCopy.entrySet()) {
66 for (final Mailbox mailbox : entry.getValue()) {
67 this.notifyHasMessage(mailbox, entry.getKey());
68 }
69 }
70 }
71 }
72
73 @Override
74 public void deliverMessages() {
75 this.currentlyDelivering = true;
76 while (!this.mailboxQueue.isEmpty()) {
77 // care must be taken here how we iterate over the mailboxes
78 // it is not okay to loop over the mailboxes at once because a mailbox may disappear from the collection as
79 // a result of delivering messages from another mailboxes under the same timestamp
80 // because of this, it is crucial that we pick the mailboxes one by one
81 final Entry<Timestamp, Set<Mailbox>> entry = this.mailboxQueue.firstEntry();
82 final Timestamp timestamp = entry.getKey();
83 final Set<Mailbox> mailboxes = entry.getValue();
84 final Mailbox mailbox = mailboxes.iterator().next();
85 mailboxes.remove(mailbox);
86 if (mailboxes.isEmpty()) {
87 this.mailboxQueue.pollFirstEntry();
88 }
89 assert mailbox instanceof TimelyMailbox;
90 /* debug */ this.currentlyDeliveredTimestamp = timestamp;
91 mailbox.deliverAll(timestamp);
92 /* debug */ this.currentlyDeliveredTimestamp = null;
93 }
94 this.currentlyDelivering = false;
95 }
96
97 @Override
98 public boolean isEmpty() {
99 return this.mailboxQueue.isEmpty();
100 }
101
102 @Override
103 public void notifyHasMessage(final Mailbox mailbox, MessageSelector kind) {
104 if (kind instanceof Timestamp) {
105 final Timestamp timestamp = (Timestamp) kind;
106 if (Options.MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS) {
107 if (timestamp.compareTo(this.currentlyDeliveredTimestamp) < 0) {
108 final Logger logger = this.representative.getContainer().getNetwork().getEngine().getLogger();
109 logger.error(
110 "[INTERNAL ERROR] Violation of differential dataflow communication schema! The communication component with representative "
111 + this.representative + " observed decreasing timestamp during message delivery!");
112 }
113 }
114 final Set<Mailbox> mailboxes = this.mailboxQueue.computeIfAbsent(timestamp, k -> {
115 if (this.nodeComparator == null) {
116 return CollectionsFactory.createSet();
117 } else {
118 return new TreeSet<Mailbox>(new Comparator<Mailbox>() {
119 @Override
120 public int compare(final Mailbox left, final Mailbox right) {
121 return nodeComparator.compare(left.getReceiver(), right.getReceiver());
122 }
123 });
124 }
125 });
126 mailboxes.add(mailbox);
127 if (!this.isEnqueued && !this.currentlyDelivering) {
128 this.tracker.activateUnenqueued(this);
129 }
130 } else {
131 throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind);
132 }
133 }
134
135 @Override
136 public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) {
137 if (kind instanceof Timestamp) {
138 final Timestamp timestamp = (Timestamp) kind;
139 this.mailboxQueue.compute(timestamp, (k, v) -> {
140 if (v == null) {
141 throw new IllegalStateException("No mailboxes registered at timestamp " + timestamp + "!");
142 }
143 if (!v.remove(mailbox)) {
144 throw new IllegalStateException(
145 "The mailbox " + mailbox + " was not registered at timestamp " + timestamp + "!");
146 }
147 if (v.isEmpty()) {
148 return null;
149 } else {
150 return v;
151 }
152 });
153 if (this.mailboxQueue.isEmpty()) {
154 this.tracker.deactivate(this);
155 }
156 } else {
157 throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind);
158 }
159 }
160
161 @Override
162 public Map<MessageSelector, Collection<Mailbox>> getMailboxes() {
163 return Collections.unmodifiableMap(this.mailboxQueue);
164 }
165
166 @Override
167 public boolean isRecursive() {
168 return !this.isSingleton;
169 }
170
171}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java
new file mode 100644
index 00000000..79179880
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java
@@ -0,0 +1,216 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import java.util.Collection;
12import java.util.List;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16import java.util.function.Function;
17
18import tools.refinery.viatra.runtime.rete.itc.alg.misc.topsort.TopologicalSorting;
19import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.rete.index.IndexerListener;
22import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer;
23import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription;
24import tools.refinery.viatra.runtime.rete.index.StandardIndexer;
25import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration;
26import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
27import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
28import tools.refinery.viatra.runtime.rete.network.Node;
29import tools.refinery.viatra.runtime.rete.network.ProductionNode;
30import tools.refinery.viatra.runtime.rete.network.StandardNode;
31import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
32import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
33import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
34import tools.refinery.viatra.runtime.rete.network.communication.NodeComparator;
35import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
36import tools.refinery.viatra.runtime.rete.single.DiscriminatorDispatcherNode;
37
38/**
39 * Timely (DDF) implementation of the {@link CommunicationTracker}.
40 *
41 * @author Tamas Szabo
42 * @since 2.3
43 */
44public class TimelyCommunicationTracker extends CommunicationTracker {
45
46 protected final TimelyConfiguration configuration;
47
48 public TimelyCommunicationTracker(final TimelyConfiguration configuration) {
49 this.configuration = configuration;
50 }
51
52 @Override
53 protected CommunicationGroup createGroup(final Node representative, final int index) {
54 final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1;
55 return new TimelyCommunicationGroup(this, representative, index, isSingleton);
56 }
57
58 @Override
59 protected void reconstructQueueContents(final Set<CommunicationGroup> oldActiveGroups) {
60 for (final CommunicationGroup oldGroup : oldActiveGroups) {
61 for (final Entry<MessageSelector, Collection<Mailbox>> entry : oldGroup.getMailboxes().entrySet()) {
62 for (final Mailbox mailbox : entry.getValue()) {
63 final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver());
64 newGroup.notifyHasMessage(mailbox, entry.getKey());
65 }
66 }
67 }
68 }
69
70 @Override
71 public Mailbox proxifyMailbox(final Node requester, final Mailbox original) {
72 final Mailbox mailboxToProxify = (original instanceof TimelyMailboxProxy)
73 ? ((TimelyMailboxProxy) original).getWrappedMailbox()
74 : original;
75 final TimestampTransformation preprocessor = getPreprocessor(requester, mailboxToProxify.getReceiver());
76 if (preprocessor == null) {
77 return mailboxToProxify;
78 } else {
79 return new TimelyMailboxProxy(mailboxToProxify, preprocessor);
80 }
81 }
82
83 @Override
84 public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) {
85 final IndexerListener listenerToProxify = (original instanceof TimelyIndexerListenerProxy)
86 ? ((TimelyIndexerListenerProxy) original).getWrappedIndexerListener()
87 : original;
88 final TimestampTransformation preprocessor = getPreprocessor(requester, listenerToProxify.getOwner());
89 if (preprocessor == null) {
90 return listenerToProxify;
91 } else {
92 return new TimelyIndexerListenerProxy(listenerToProxify, preprocessor);
93 }
94 }
95
96 protected TimestampTransformation getPreprocessor(final Node source, final Node target) {
97 final Node effectiveSource = source instanceof SpecializedProjectionIndexer
98 ? ((SpecializedProjectionIndexer) source).getActiveNode()
99 : source;
100 final CommunicationGroup sourceGroup = this.getGroup(effectiveSource);
101 final CommunicationGroup targetGroup = this.getGroup(target);
102
103 if (sourceGroup != null && targetGroup != null) {
104 // during RETE construction, the groups may be still null
105 if (sourceGroup != targetGroup && sourceGroup.isRecursive()) {
106 // targetGroup is a successor SCC of sourceGroup
107 // and sourceGroup is a recursive SCC
108 // then we need to zero out the timestamps
109 return TimestampTransformation.RESET;
110 }
111 if (sourceGroup == targetGroup && target instanceof ProductionNode) {
112 // if requester and receiver are in the same SCC
113 // and receiver is a production node
114 // then we need to increment the timestamps
115 return TimestampTransformation.INCREMENT;
116 }
117 }
118
119 return null;
120 }
121
122 @Override
123 protected void postProcessNode(final Node node) {
124 if (node instanceof NetworkStructureChangeSensitiveNode) {
125 ((NetworkStructureChangeSensitiveNode) node).networkStructureChanged();
126 }
127 }
128
129 @Override
130 protected void postProcessGroup(final CommunicationGroup group) {
131 if (this.configuration.getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) {
132 final Node representative = group.getRepresentative();
133 final Set<Node> groupMembers = this.sccInformationProvider.sccs.getPartition(representative);
134 if (groupMembers.size() > 1) {
135 final Graph<Node> graph = new Graph<Node>();
136
137 for (final Node node : groupMembers) {
138 graph.insertNode(node);
139 }
140
141 for (final Node source : groupMembers) {
142 for (final Node target : this.dependencyGraph.getTargetNodes(source)) {
143 // (1) the edge is not a recursion cut point
144 // (2) the edge is within this group
145 if (!this.isRecursionCutPoint(source, target) && groupMembers.contains(target)) {
146 graph.insertEdge(source, target);
147 }
148 }
149 }
150
151 final List<Node> orderedNodes = TopologicalSorting.compute(graph);
152 final Map<Node, Integer> nodeMap = CollectionsFactory.createMap();
153 int identifier = 0;
154 for (final Node orderedNode : orderedNodes) {
155 nodeMap.put(orderedNode, identifier++);
156 }
157
158 ((TimelyCommunicationGroup) group).setComparatorAndReorderMailboxes(new NodeComparator(nodeMap));
159 }
160 }
161 }
162
163 /**
164 * This static field is used for debug purposes in the DotGenerator.
165 */
166 public static final Function<Node, Function<Node, String>> EDGE_LABEL_FUNCTION = new Function<Node, Function<Node, String>>() {
167
168 @Override
169 public Function<Node, String> apply(final Node source) {
170 return new Function<Node, String>() {
171 @Override
172 public String apply(final Node target) {
173 if (source instanceof SpecializedProjectionIndexer) {
174 final Collection<ListenerSubscription> subscriptions = ((SpecializedProjectionIndexer) source)
175 .getSubscriptions();
176 for (final ListenerSubscription subscription : subscriptions) {
177 if (subscription.getListener().getOwner() == target
178 && subscription.getListener() instanceof TimelyIndexerListenerProxy) {
179 return ((TimelyIndexerListenerProxy) subscription.getListener()).preprocessor
180 .toString();
181 }
182 }
183 }
184 if (source instanceof StandardIndexer) {
185 final Collection<IndexerListener> listeners = ((StandardIndexer) source).getListeners();
186 for (final IndexerListener listener : listeners) {
187 if (listener.getOwner() == target && listener instanceof TimelyIndexerListenerProxy) {
188 return ((TimelyIndexerListenerProxy) listener).preprocessor.toString();
189 }
190 }
191 }
192 if (source instanceof StandardNode) {
193 final Collection<Mailbox> mailboxes = ((StandardNode) source).getChildMailboxes();
194 for (final Mailbox mailbox : mailboxes) {
195 if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) {
196 return ((TimelyMailboxProxy) mailbox).preprocessor.toString();
197 }
198 }
199 }
200 if (source instanceof DiscriminatorDispatcherNode) {
201 final Collection<Mailbox> mailboxes = ((DiscriminatorDispatcherNode) source)
202 .getBucketMailboxes().values();
203 for (final Mailbox mailbox : mailboxes) {
204 if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) {
205 return ((TimelyMailboxProxy) mailbox).preprocessor.toString();
206 }
207 }
208 }
209 return null;
210 }
211 };
212 }
213
214 };
215
216}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java
new file mode 100644
index 00000000..e8fbf84e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.viatra.runtime.matchers.util.Direction;
13import tools.refinery.viatra.runtime.matchers.util.Preconditions;
14import tools.refinery.viatra.runtime.rete.index.IndexerListener;
15import tools.refinery.viatra.runtime.rete.network.Node;
16import tools.refinery.viatra.runtime.rete.network.ProductionNode;
17import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
18
19/**
20 * A timely proxy for another {@link IndexerListener}, which performs some preprocessing
21 * on the differential timestamps before passing it on to the real recipient.
22 * <p>
23 * These proxies are used on edges leading into {@link ProductionNode}s. Because {@link ProductionNode}s
24 * never ask back the indexer for its contents, there is no need to also apply the proxy on that direction.
25 *
26 * @author Tamas Szabo
27 * @since 2.3
28 */
29public class TimelyIndexerListenerProxy implements IndexerListener {
30
31 protected final TimestampTransformation preprocessor;
32 protected final IndexerListener wrapped;
33
34 public TimelyIndexerListenerProxy(final IndexerListener wrapped,
35 final TimestampTransformation preprocessor) {
36 Preconditions.checkArgument(!(wrapped instanceof TimelyIndexerListenerProxy), "Proxy in a proxy is not allowed!");
37 this.wrapped = wrapped;
38 this.preprocessor = preprocessor;
39 }
40
41 public IndexerListener getWrappedIndexerListener() {
42 return wrapped;
43 }
44
45 @Override
46 public Node getOwner() {
47 return this.wrapped.getOwner();
48 }
49
50 @Override
51 public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, final Tuple signature,
52 final boolean change, final Timestamp timestamp) {
53 this.wrapped.notifyIndexerUpdate(direction, updateElement, signature, change, preprocessor.process(timestamp));
54 }
55
56 @Override
57 public String toString() {
58 return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString();
59 }
60
61 @Override
62 public boolean equals(final Object obj) {
63 if (obj == null || obj.getClass() != this.getClass()) {
64 return false;
65 } else if (obj == this) {
66 return true;
67 } else {
68 final TimelyIndexerListenerProxy that = (TimelyIndexerListenerProxy) obj;
69 return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor;
70 }
71 }
72
73 @Override
74 public int hashCode() {
75 int hash = 1;
76 hash = hash * 17 + this.wrapped.hashCode();
77 hash = hash * 31 + this.preprocessor.hashCode();
78 return hash;
79 }
80
81}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java
new file mode 100644
index 00000000..550bfbeb
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.viatra.runtime.matchers.util.Direction;
13import tools.refinery.viatra.runtime.matchers.util.Preconditions;
14import tools.refinery.viatra.runtime.rete.network.Receiver;
15import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
16import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
17import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
18import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
19
20/**
21 * A timely proxy for another {@link Mailbox}, which performs some preprocessing
22 * on the differential timestamps before passing it on to the real recipient.
23 *
24 * @author Tamas Szabo
25 * @since 2.3
26 */
27public class TimelyMailboxProxy implements Mailbox {
28
29 protected final TimestampTransformation preprocessor;
30 protected final Mailbox wrapped;
31
32 public TimelyMailboxProxy(final Mailbox wrapped, final TimestampTransformation preprocessor) {
33 Preconditions.checkArgument(!(wrapped instanceof TimelyMailboxProxy), "Proxy in a proxy is not allowed!");
34 this.wrapped = wrapped;
35 this.preprocessor = preprocessor;
36 }
37
38 public Mailbox getWrappedMailbox() {
39 return wrapped;
40 }
41
42 @Override
43 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
44 this.wrapped.postMessage(direction, update, preprocessor.process(timestamp));
45 }
46
47 @Override
48 public String toString() {
49 return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString();
50 }
51
52 @Override
53 public void clear() {
54 this.wrapped.clear();
55 }
56
57 @Override
58 public void deliverAll(final MessageSelector selector) {
59 this.wrapped.deliverAll(selector);
60 }
61
62 @Override
63 public CommunicationGroup getCurrentGroup() {
64 return this.wrapped.getCurrentGroup();
65 }
66
67 @Override
68 public void setCurrentGroup(final CommunicationGroup group) {
69 this.wrapped.setCurrentGroup(group);
70 }
71
72 @Override
73 public Receiver getReceiver() {
74 return this.wrapped.getReceiver();
75 }
76
77 @Override
78 public boolean isEmpty() {
79 return this.wrapped.isEmpty();
80 }
81
82 @Override
83 public boolean equals(final Object obj) {
84 if (obj == null || obj.getClass() != this.getClass()) {
85 return false;
86 } else if (obj == this) {
87 return true;
88 } else {
89 final TimelyMailboxProxy that = (TimelyMailboxProxy) obj;
90 return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor;
91 }
92 }
93
94 @Override
95 public int hashCode() {
96 int hash = 1;
97 hash = hash * 17 + this.wrapped.hashCode();
98 hash = hash * 31 + this.preprocessor.hashCode();
99 return hash;
100 }
101
102}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java
new file mode 100644
index 00000000..8929eb5c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.communication.timely;
10
11import tools.refinery.viatra.runtime.rete.network.Node;
12import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
13
14/**
15 * Values of this enum perform different kind of preprocessing on {@link Timestamp}s.
16 * This is used on edges leading in and out from {@link Node}s in recursive {@link TimelyCommunicationGroup}s.
17 *
18 * @author Tamas Szabo
19 * @since 2.3
20 */
21public enum TimestampTransformation {
22
23 INCREMENT {
24 @Override
25 public Timestamp process(final Timestamp timestamp) {
26 return new Timestamp(timestamp.getValue() + 1);
27 }
28
29 @Override
30 public String toString() {
31 return "INCREMENT";
32 }
33 },
34 RESET {
35 @Override
36 public Timestamp process(final Timestamp timestamp) {
37 return Timestamp.ZERO;
38 }
39
40 @Override
41 public String toString() {
42 return "RESET";
43 }
44 };
45
46 public abstract Timestamp process(final Timestamp timestamp);
47
48}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java
new file mode 100644
index 00000000..d6312671
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.delayed;
10
11import java.util.Collection;
12import java.util.Map;
13import java.util.Map.Entry;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.matchers.util.Signed;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.network.Network;
20import tools.refinery.viatra.runtime.rete.network.Node;
21import tools.refinery.viatra.runtime.rete.network.Receiver;
22import tools.refinery.viatra.runtime.rete.network.ReteContainer;
23import tools.refinery.viatra.runtime.rete.network.Supplier;
24import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
27
28/**
29 * Instances of this class are responsible for initializing a {@link Receiver} with the contents of a {@link Supplier}.
30 * However, due to the dynamic nature of the Rete {@link Network} and to the fact that certain {@link Node}s in the
31 * {@link Network} are sensitive to the shape of the {@link Network}, the commands must be delayed until the
32 * construction of the {@link Network} has stabilized.
33 *
34 * @author Tamas Szabo
35 * @since 2.3
36 */
37public abstract class DelayedCommand implements Runnable {
38
39 protected final Supplier supplier;
40 protected final Receiver receiver;
41 protected final Direction direction;
42 protected final ReteContainer container;
43
44 public DelayedCommand(final Supplier supplier, final Receiver receiver, final Direction direction,
45 final ReteContainer container) {
46 this.supplier = supplier;
47 this.receiver = receiver;
48 this.direction = direction;
49 this.container = container;
50 }
51
52 @Override
53 public void run() {
54 final CommunicationTracker tracker = this.container.getCommunicationTracker();
55 final Mailbox mailbox = tracker.proxifyMailbox(this.supplier, this.receiver.getMailbox());
56
57 if (this.isTimestampAware()) {
58 final Map<Tuple, Timeline<Timestamp>> contents = this.container.pullContentsWithTimeline(this.supplier,
59 false);
60 for (final Entry<Tuple, Timeline<Timestamp>> entry : contents.entrySet()) {
61 for (final Signed<Timestamp> change : entry.getValue().asChangeSequence()) {
62 mailbox.postMessage(change.getDirection().multiply(this.direction), entry.getKey(),
63 change.getPayload());
64 }
65 }
66 } else {
67 final Collection<Tuple> contents = this.container.pullContents(this.supplier, false);
68 for (final Tuple tuple : contents) {
69 mailbox.postMessage(this.direction, tuple, Timestamp.ZERO);
70 }
71 }
72 }
73
74 @Override
75 public String toString() {
76 return this.supplier + " -> " + this.receiver.toString();
77 }
78
79 protected abstract boolean isTimestampAware();
80
81}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java
new file mode 100644
index 00000000..1bfdbec6
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.delayed;
10
11import tools.refinery.viatra.runtime.matchers.util.Direction;
12import tools.refinery.viatra.runtime.rete.network.Receiver;
13import tools.refinery.viatra.runtime.rete.network.ReteContainer;
14import tools.refinery.viatra.runtime.rete.network.Supplier;
15
16public class DelayedConnectCommand extends DelayedCommand {
17
18 public DelayedConnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container) {
19 super(supplier, receiver, Direction.INSERT, container);
20 }
21
22 @Override
23 protected boolean isTimestampAware() {
24 return this.container.isTimelyEvaluation() && this.container.getCommunicationTracker().areInSameGroup(this.supplier, this.receiver);
25 }
26
27}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java
new file mode 100644
index 00000000..5825a971
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.delayed;
10
11import tools.refinery.viatra.runtime.matchers.util.Direction;
12import tools.refinery.viatra.runtime.rete.network.Receiver;
13import tools.refinery.viatra.runtime.rete.network.ReteContainer;
14import tools.refinery.viatra.runtime.rete.network.Supplier;
15
16public class DelayedDisconnectCommand extends DelayedCommand {
17
18 protected final boolean wasInSameSCC;
19
20 public DelayedDisconnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container, final boolean wasInSameSCC) {
21 super(supplier, receiver, Direction.DELETE, container);
22 this.wasInSameSCC = wasInSameSCC;
23 }
24
25 @Override
26 protected boolean isTimestampAware() {
27 return this.wasInSameSCC;
28 }
29
30}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java
new file mode 100644
index 00000000..da9bc47e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.indexer;
10
11import java.util.Collections;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
16
17/**
18 * @author Tamas Szabo
19 * @since 2.0
20 */
21public class DefaultMessageIndexer implements MessageIndexer {
22
23 protected final Map<Tuple, Integer> indexer;
24
25 public DefaultMessageIndexer() {
26 this.indexer = CollectionsFactory.createMap();
27 }
28
29 public Map<Tuple, Integer> getTuples() {
30 return Collections.unmodifiableMap(this.indexer);
31 }
32
33 @Override
34 public int getCount(final Tuple update) {
35 final Integer count = getTuples().get(update);
36 if (count == null) {
37 return 0;
38 } else {
39 return count;
40 }
41 }
42
43 @Override
44 public void insert(final Tuple update) {
45 update(update, 1);
46 }
47
48 @Override
49 public void delete(final Tuple update) {
50 update(update, -1);
51 }
52
53 @Override
54 public void update(final Tuple update, final int delta) {
55 final Integer oldCount = this.indexer.get(update);
56 final int newCount = (oldCount == null ? 0 : oldCount) + delta;
57 if (newCount == 0) {
58 this.indexer.remove(update);
59 } else {
60 this.indexer.put(update, newCount);
61 }
62 }
63
64 @Override
65 public boolean isEmpty() {
66 return this.indexer.isEmpty();
67 }
68
69 @Override
70 public void clear() {
71 this.indexer.clear();
72 }
73
74}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java
new file mode 100644
index 00000000..80271252
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.indexer;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
18
19/**
20 * @author Tamas Szabo
21 * @since 2.0
22 */
23public class GroupBasedMessageIndexer implements MessageIndexer {
24
25 protected final Map<Tuple, DefaultMessageIndexer> indexer;
26 protected final TupleMask groupMask;
27
28 public GroupBasedMessageIndexer(final TupleMask groupMask) {
29 this.indexer = CollectionsFactory.createMap();
30 this.groupMask = groupMask;
31 }
32
33 public Map<Tuple, Integer> getTuplesByGroup(final Tuple group) {
34 final DefaultMessageIndexer values = this.indexer.get(group);
35 if (values == null) {
36 return Collections.emptyMap();
37 } else {
38 return Collections.unmodifiableMap(values.getTuples());
39 }
40 }
41
42 @Override
43 public int getCount(final Tuple update) {
44 final Tuple group = this.groupMask.transform(update);
45 final Integer count = getTuplesByGroup(group).get(update);
46 if (count == null) {
47 return 0;
48 } else {
49 return count;
50 }
51 }
52
53 public Set<Tuple> getGroups() {
54 return Collections.unmodifiableSet(this.indexer.keySet());
55 }
56
57 @Override
58 public void insert(final Tuple update) {
59 update(update, 1);
60 }
61
62 @Override
63 public void delete(final Tuple update) {
64 update(update, -1);
65 }
66
67 @Override
68 public void update(final Tuple update, final int delta) {
69 final Tuple group = this.groupMask.transform(update);
70 DefaultMessageIndexer valueIndexer = this.indexer.get(group);
71
72 if (valueIndexer == null) {
73 valueIndexer = new DefaultMessageIndexer();
74 this.indexer.put(group, valueIndexer);
75 }
76
77 valueIndexer.update(update, delta);
78
79 // it may happen that the indexer becomes empty as a result of the update
80 if (valueIndexer.isEmpty()) {
81 this.indexer.remove(group);
82 }
83 }
84
85 @Override
86 public boolean isEmpty() {
87 return this.indexer.isEmpty();
88 }
89
90 @Override
91 public void clear() {
92 this.indexer.clear();
93 }
94
95}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java
new file mode 100644
index 00000000..271aaa44
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.indexer;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.viatra.runtime.matchers.util.Clearable;
13import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
14
15/**
16 * A message indexer is used by {@link Mailbox}es to index their contents.
17 *
18 * @author Tamas Szabo
19 * @since 2.0
20 */
21public interface MessageIndexer extends Clearable {
22
23 public void insert(final Tuple update);
24
25 public void delete(final Tuple update);
26
27 public void update(final Tuple update, final int delta);
28
29 public boolean isEmpty();
30
31 public int getCount(final Tuple update);
32
33}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java
new file mode 100644
index 00000000..99097f56
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox;
10
11import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
12import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy;
13import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
14
15/**
16 * An adaptable mailbox can be wrapped by another mailbox to act in behalf of that. The significance of the adaptation
17 * is that the adaptee will notify the {@link CommunicationTracker} about updates by promoting the adapter itself.
18 * Adaptable mailboxes are used by the {@link BehaviorChangingMailbox}.
19 *
20 * Compare this with {@link TimelyMailboxProxy}. That one also wraps another mailbox in order to
21 * perform preprocessing on the messages sent to the original recipient.
22 *
23 * @author Tamas Szabo
24 * @since 2.0
25 */
26public interface AdaptableMailbox extends Mailbox {
27
28 public Mailbox getAdapter();
29
30 public void setAdapter(final Mailbox adapter);
31
32}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java
new file mode 100644
index 00000000..8797e254
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox;
10
11import tools.refinery.viatra.runtime.rete.network.Receiver;
12import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
13
14/**
15 * A fall through capable mailbox can directly call the update method of its {@link Receiver} instead of using the
16 * standard post-deliver mailbox semantics. If the fall through flag is set to true, the mailbox uses direct delivery,
17 * otherwise it operates in the original behavior. The fall through operation is preferable whenever applicable because
18 * it improves performance. The fall through flag is controlled by the {@link CommunicationTracker} based on the
19 * receiver node type and network topology.
20 *
21 * @author Tamas Szabo
22 * @since 2.2
23 */
24public interface FallThroughCapableMailbox extends Mailbox {
25
26 public boolean isFallThrough();
27
28 public void setFallThrough(final boolean fallThrough);
29
30}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java
new file mode 100644
index 00000000..05005974
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java
@@ -0,0 +1,78 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.viatra.runtime.matchers.util.Clearable;
13import tools.refinery.viatra.runtime.matchers.util.Direction;
14import tools.refinery.viatra.runtime.rete.network.IGroupable;
15import tools.refinery.viatra.runtime.rete.network.Receiver;
16import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
17import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
18import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
19
20/**
21 * A mailbox is associated with every {@link Receiver}. Messages can be sent to a {@link Receiver} by posting them into
22 * the mailbox. Different mailbox implementations may differ in the way how they deliver the posted messages.
23 *
24 * @author Tamas Szabo
25 * @since 2.0
26 *
27 */
28public interface Mailbox extends Clearable, IGroupable {
29
30 /**
31 * Posts a new message to this mailbox.
32 *
33 * @param direction
34 * the direction of the update
35 * @param update
36 * the update element
37 * @since 2.4
38 */
39 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp);
40
41 /**
42 * Delivers all messages according to the given selector from this mailbox. The selector can also be null. In this case, no
43 * special separation is expected between the messages.
44 *
45 * @param selector the message selector
46 */
47 public void deliverAll(final MessageSelector selector);
48
49 /**
50 * Returns the {@link Receiver} of this mailbox.
51 *
52 * @return the receiver
53 */
54 public Receiver getReceiver();
55
56 /**
57 * Returns the {@link CommunicationGroup} of the receiver of this mailbox.
58 *
59 * @return the communication group
60 */
61 public CommunicationGroup getCurrentGroup();
62
63 /**
64 * Sets the {@link CommunicationGroup} that the receiver of this mailbox is associated with.
65 *
66 * @param group
67 * the communication group
68 */
69 public void setCurrentGroup(final CommunicationGroup group);
70
71 /**
72 * Returns true if this mailbox is empty.
73 *
74 * @return
75 */
76 public boolean isEmpty();
77
78}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java
new file mode 100644
index 00000000..2c5255fb
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox;
10
11import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer;
12
13/**
14 * A factory used to create message indexers for {@link Mailbox}es.
15 *
16 * @author Tamas Szabo
17 * @since 2.0
18 */
19public interface MessageIndexerFactory<I extends MessageIndexer> {
20
21 public I create();
22
23}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java
new file mode 100644
index 00000000..1e1ada71
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java
@@ -0,0 +1,109 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timeless;
10
11import tools.refinery.viatra.runtime.rete.network.Receiver;
12import tools.refinery.viatra.runtime.rete.network.ReteContainer;
13import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
14import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer;
15import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
16import tools.refinery.viatra.runtime.rete.network.mailbox.MessageIndexerFactory;
17
18/**
19 * An abstract mailbox implementation that is capable of splitting update messages based on some form of monotonicity
20 * (anti-monotone and monotone). The monotonicity is either defined by the less or equal operator of a poset or, it can
21 * be the standard subset ordering among sets of tuples.
22 *
23 * @author Tamas Szabo
24 * @since 2.0
25 *
26 */
27public abstract class AbstractUpdateSplittingMailbox<IndexerType extends MessageIndexer, ReceiverType extends Receiver> implements Mailbox {
28
29 protected IndexerType monotoneQueue;
30 protected IndexerType antiMonotoneQueue;
31 protected IndexerType monotoneBuffer;
32 protected IndexerType antiMonotoneBuffer;
33 protected boolean deliveringMonotone;
34 protected boolean deliveringAntiMonotone;
35 protected final ReceiverType receiver;
36 protected final ReteContainer container;
37 protected CommunicationGroup group;
38
39 public AbstractUpdateSplittingMailbox(final ReceiverType receiver, final ReteContainer container,
40 final MessageIndexerFactory<IndexerType> factory) {
41 this.receiver = receiver;
42 this.container = container;
43 this.monotoneQueue = factory.create();
44 this.antiMonotoneQueue = factory.create();
45 this.monotoneBuffer = factory.create();
46 this.antiMonotoneBuffer = factory.create();
47 this.deliveringMonotone = false;
48 this.deliveringAntiMonotone = false;
49 }
50
51 protected void swapAndClearMonotone() {
52 final IndexerType tmp = this.monotoneQueue;
53 this.monotoneQueue = this.monotoneBuffer;
54 this.monotoneBuffer = tmp;
55 this.monotoneBuffer.clear();
56 }
57
58 protected void swapAndClearAntiMonotone() {
59 final IndexerType tmp = this.antiMonotoneQueue;
60 this.antiMonotoneQueue = this.antiMonotoneBuffer;
61 this.antiMonotoneBuffer = tmp;
62 this.antiMonotoneBuffer.clear();
63 }
64
65 protected IndexerType getActiveMonotoneQueue() {
66 if (this.deliveringMonotone) {
67 return this.monotoneBuffer;
68 } else {
69 return this.monotoneQueue;
70 }
71 }
72
73 protected IndexerType getActiveAntiMonotoneQueue() {
74 if (this.deliveringAntiMonotone) {
75 return this.antiMonotoneBuffer;
76 } else {
77 return this.antiMonotoneQueue;
78 }
79 }
80
81 @Override
82 public ReceiverType getReceiver() {
83 return this.receiver;
84 }
85
86 @Override
87 public void clear() {
88 this.monotoneQueue.clear();
89 this.antiMonotoneQueue.clear();
90 this.monotoneBuffer.clear();
91 this.antiMonotoneBuffer.clear();
92 }
93
94 @Override
95 public boolean isEmpty() {
96 return this.getActiveMonotoneQueue().isEmpty() && this.getActiveAntiMonotoneQueue().isEmpty();
97 }
98
99 @Override
100 public CommunicationGroup getCurrentGroup() {
101 return this.group;
102 }
103
104 @Override
105 public void setCurrentGroup(final CommunicationGroup group) {
106 this.group = group;
107 }
108
109}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java
new file mode 100644
index 00000000..fe822d7c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timeless;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12import tools.refinery.viatra.runtime.matchers.util.Direction;
13import tools.refinery.viatra.runtime.rete.network.Node;
14import tools.refinery.viatra.runtime.rete.network.Receiver;
15import tools.refinery.viatra.runtime.rete.network.ReteContainer;
16import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
17import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
18import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker;
21import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox;
22import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox;
23
24/**
25 * This mailbox changes its behavior based on the position of its {@link Receiver} in the network topology.
26 * It either behaves as a {@link DefaultMailbox} or as an {@link UpdateSplittingMailbox}. The decision is made by the
27 * {@link CommunicationTracker}, see {@link TimelessCommunicationTracker#postProcessNode(Node)} for more details.
28 *
29 * @author Tamas Szabo
30 */
31public class BehaviorChangingMailbox implements FallThroughCapableMailbox {
32
33 protected boolean fallThrough;
34 protected boolean split;
35 protected AdaptableMailbox wrapped;
36 protected final Receiver receiver;
37 protected final ReteContainer container;
38 protected CommunicationGroup group;
39
40 public BehaviorChangingMailbox(final Receiver receiver, final ReteContainer container) {
41 this.fallThrough = false;
42 this.split = false;
43 this.receiver = receiver;
44 this.container = container;
45 this.wrapped = new DefaultMailbox(receiver, container);
46 this.wrapped.setAdapter(this);
47 }
48
49 @Override
50 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
51 if (this.fallThrough && !this.container.isExecutingDelayedCommands()) {
52 // disable fall through while we are in the middle of executing delayed construction commands
53 this.receiver.update(direction, update, timestamp);
54 } else {
55 this.wrapped.postMessage(direction, update, timestamp);
56 }
57 }
58
59 @Override
60 public void deliverAll(final MessageSelector kind) {
61 this.wrapped.deliverAll(kind);
62 }
63
64 @Override
65 public String toString() {
66 return "A_MBOX -> " + this.wrapped;
67 }
68
69 public void setSplitFlag(final boolean splitValue) {
70 if (this.split != splitValue) {
71 assert isEmpty();
72 if (splitValue) {
73 this.wrapped = new UpdateSplittingMailbox(this.receiver, this.container);
74 } else {
75 this.wrapped = new DefaultMailbox(this.receiver, this.container);
76 }
77 this.wrapped.setAdapter(this);
78 this.split = splitValue;
79 }
80 }
81
82 @Override
83 public boolean isEmpty() {
84 return this.wrapped.isEmpty();
85 }
86
87 @Override
88 public void clear() {
89 this.wrapped.clear();
90 }
91
92 @Override
93 public Receiver getReceiver() {
94 return this.receiver;
95 }
96
97 @Override
98 public CommunicationGroup getCurrentGroup() {
99 return this.group;
100 }
101
102 @Override
103 public void setCurrentGroup(final CommunicationGroup group) {
104 this.group = group;
105 }
106
107 @Override
108 public boolean isFallThrough() {
109 return this.fallThrough;
110 }
111
112 @Override
113 public void setFallThrough(final boolean fallThrough) {
114 this.fallThrough = fallThrough;
115 }
116
117} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java
new file mode 100644
index 00000000..baf7270f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java
@@ -0,0 +1,163 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timeless;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
15import tools.refinery.viatra.runtime.matchers.util.Direction;
16import tools.refinery.viatra.runtime.rete.network.Receiver;
17import tools.refinery.viatra.runtime.rete.network.ReteContainer;
18import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
19import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
20import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector;
21import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
22import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox;
23import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
24
25/**
26 * Default mailbox implementation.
27 * <p>
28 * Usually, the mailbox performs counting of messages so that they can cancel each other out. However, if marked as a
29 * fall-through mailbox, than update messages are delivered directly to the receiver node to reduce overhead.
30 *
31 * @author Tamas Szabo
32 * @since 2.0
33 */
34public class DefaultMailbox implements AdaptableMailbox {
35
36 private static int SIZE_TRESHOLD = 127;
37
38 protected Map<Tuple, Integer> queue;
39 protected Map<Tuple, Integer> buffer;
40 protected final Receiver receiver;
41 protected final ReteContainer container;
42 protected boolean delivering;
43 protected Mailbox adapter;
44 protected CommunicationGroup group;
45
46 public DefaultMailbox(final Receiver receiver, final ReteContainer container) {
47 this.receiver = receiver;
48 this.container = container;
49 this.queue = CollectionsFactory.createMap();
50 this.buffer = CollectionsFactory.createMap();
51 this.adapter = this;
52 }
53
54 protected Map<Tuple, Integer> getActiveQueue() {
55 if (this.delivering) {
56 return this.buffer;
57 } else {
58 return this.queue;
59 }
60 }
61
62 @Override
63 public Mailbox getAdapter() {
64 return this.adapter;
65 }
66
67 @Override
68 public void setAdapter(final Mailbox adapter) {
69 this.adapter = adapter;
70 }
71
72 @Override
73 public boolean isEmpty() {
74 return getActiveQueue().isEmpty();
75 }
76
77 @Override
78 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
79 final Map<Tuple, Integer> activeQueue = getActiveQueue();
80 final boolean wasEmpty = activeQueue.isEmpty();
81
82 boolean significantChange = false;
83 Integer count = activeQueue.get(update);
84 if (count == null) {
85 count = 0;
86 significantChange = true;
87 }
88
89 if (direction == Direction.DELETE) {
90 count--;
91 } else {
92 count++;
93 }
94
95 if (count == 0) {
96 activeQueue.remove(update);
97 significantChange = true;
98 } else {
99 activeQueue.put(update, count);
100 }
101
102 if (significantChange) {
103 final Mailbox targetMailbox = this.adapter;
104 final CommunicationGroup targetGroup = this.adapter.getCurrentGroup();
105
106 if (wasEmpty) {
107 targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.DEFAULT);
108 } else if (activeQueue.isEmpty()) {
109 targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.DEFAULT);
110 }
111 }
112 }
113
114 @Override
115 public void deliverAll(final MessageSelector kind) {
116 if (kind == PhasedSelector.DEFAULT) {
117 // use the buffer during delivering so that there is a clear
118 // separation between the stages
119 this.delivering = true;
120 this.receiver.batchUpdate(this.queue.entrySet(), Timestamp.ZERO);
121 this.delivering = false;
122
123 if (queue.size() > SIZE_TRESHOLD) {
124 this.queue = this.buffer;
125 this.buffer = CollectionsFactory.createMap();
126 } else {
127 this.queue.clear();
128 final Map<Tuple, Integer> tmpQueue = this.queue;
129 this.queue = this.buffer;
130 this.buffer = tmpQueue;
131 }
132 } else {
133 throw new IllegalArgumentException("Unsupported message kind " + kind);
134 }
135 }
136
137 @Override
138 public String toString() {
139 return "D_MBOX (" + this.receiver + ") " + this.getActiveQueue();
140 }
141
142 @Override
143 public Receiver getReceiver() {
144 return this.receiver;
145 }
146
147 @Override
148 public void clear() {
149 this.queue.clear();
150 this.buffer.clear();
151 }
152
153 @Override
154 public CommunicationGroup getCurrentGroup() {
155 return this.group;
156 }
157
158 @Override
159 public void setCurrentGroup(final CommunicationGroup group) {
160 this.group = group;
161 }
162
163}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java
new file mode 100644
index 00000000..50d19882
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java
@@ -0,0 +1,218 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timeless;
10
11import java.util.HashSet;
12import java.util.Map.Entry;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
23import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector;
24import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
25import tools.refinery.viatra.runtime.rete.network.indexer.GroupBasedMessageIndexer;
26
27/**
28 * A monotonicity aware mailbox implementation. The mailbox uses an {@link IPosetComparator} to identify if a pair of
29 * REVOKE - INSERT updates represent a monotone change pair. The mailbox is used by {@link PosetAwareReceiver}s.
30 *
31 * @author Tamas Szabo
32 * @since 2.0
33 */
34public class PosetAwareMailbox extends AbstractUpdateSplittingMailbox<GroupBasedMessageIndexer, PosetAwareReceiver> {
35
36 protected final TupleMask groupMask;
37
38 public PosetAwareMailbox(final PosetAwareReceiver receiver, final ReteContainer container) {
39 super(receiver, container, () -> new GroupBasedMessageIndexer(receiver.getCoreMask()));
40 this.groupMask = receiver.getCoreMask();
41 }
42
43 @Override
44 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
45 final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue();
46 final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue();
47 final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0;
48 final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0;
49 final TupleMask coreMask = this.receiver.getCoreMask();
50
51 // it cannot happen that it was present in both
52 assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone);
53
54 if (direction == Direction.INSERT) {
55 if (wasPresentAsAntiMonotone) {
56 // it was an anti-monotone one before
57 antiMonotoneQueue.insert(update);
58 } else {
59 // it was a monotone one before or did not exist at all
60 monotoneQueue.insert(update);
61
62 // if it was not present in the monotone queue before, then
63 // we need to check whether it makes REVOKE updates monotone
64 if (!wasPresentAsMonotone) {
65 final Set<Tuple> counterParts = tryFindCounterPart(update, false, true);
66 for (final Tuple counterPart : counterParts) {
67 final int count = antiMonotoneQueue.getCount(counterPart);
68 assert count < 0;
69 antiMonotoneQueue.update(counterPart, -count);
70 monotoneQueue.update(counterPart, count);
71 }
72 }
73 }
74 } else {
75 if (wasPresentAsAntiMonotone) {
76 // it was an anti-monotone one before
77 antiMonotoneQueue.delete(update);
78 } else if (wasPresentAsMonotone) {
79 // it was a monotone one before
80 monotoneQueue.delete(update);
81
82 // and we need to check whether the monotone REVOKE updates
83 // still have a reinforcing counterpart
84 final Set<Tuple> candidates = new HashSet<Tuple>();
85 final Tuple key = coreMask.transform(update);
86 for (final Entry<Tuple, Integer> entry : monotoneQueue.getTuplesByGroup(key).entrySet()) {
87 if (entry.getValue() < 0) {
88 final Tuple candidate = entry.getKey();
89 final Set<Tuple> counterParts = tryFindCounterPart(candidate, true, false);
90 if (counterParts.isEmpty()) {
91 // all of them are gone
92 candidates.add(candidate);
93 }
94 }
95 }
96
97 // move the candidates from the monotone queue to the
98 // anti-monotone queue because they do not have a
99 // counterpart anymore
100 for (final Tuple candidate : candidates) {
101 final int count = monotoneQueue.getCount(candidate);
102 assert count < 0;
103 monotoneQueue.update(candidate, -count);
104 antiMonotoneQueue.update(candidate, count);
105 }
106 } else {
107 // it did not exist before
108 final Set<Tuple> counterParts = tryFindCounterPart(update, true, false);
109 if (counterParts.isEmpty()) {
110 // there is no tuple that would make this update monotone
111 antiMonotoneQueue.delete(update);
112 } else {
113 // there is a reinforcing counterpart
114 monotoneQueue.delete(update);
115 }
116 }
117 }
118
119 if (antiMonotoneQueue.isEmpty()) {
120 this.group.notifyLostAllMessages(this, PhasedSelector.ANTI_MONOTONE);
121 } else {
122 this.group.notifyHasMessage(this, PhasedSelector.ANTI_MONOTONE);
123 }
124
125 if (monotoneQueue.isEmpty()) {
126 this.group.notifyLostAllMessages(this, PhasedSelector.MONOTONE);
127 } else {
128 this.group.notifyHasMessage(this, PhasedSelector.MONOTONE);
129 }
130 }
131
132 protected Set<Tuple> tryFindCounterPart(final Tuple first, final boolean findPositiveCounterPart,
133 final boolean findAllCounterParts) {
134 final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue();
135 final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue();
136 final TupleMask coreMask = this.receiver.getCoreMask();
137 final TupleMask posetMask = this.receiver.getPosetMask();
138 final IPosetComparator posetComparator = this.receiver.getPosetComparator();
139 final Set<Tuple> result = CollectionsFactory.createSet();
140 final Tuple firstKey = coreMask.transform(first);
141 final Tuple firstValue = posetMask.transform(first);
142
143 if (findPositiveCounterPart) {
144 for (final Entry<Tuple, Integer> entry : monotoneQueue.getTuplesByGroup(firstKey).entrySet()) {
145 final Tuple secondValue = posetMask.transform(entry.getKey());
146 if (entry.getValue() > 0 && posetComparator.isLessOrEqual(firstValue, secondValue)) {
147 result.add(entry.getKey());
148 if (!findAllCounterParts) {
149 return result;
150 }
151 }
152 }
153 } else {
154 for (final Entry<Tuple, Integer> entry : antiMonotoneQueue.getTuplesByGroup(firstKey).entrySet()) {
155 final Tuple secondValue = posetMask.transform(entry.getKey());
156 if (posetComparator.isLessOrEqual(secondValue, firstValue)) {
157 result.add(entry.getKey());
158 if (!findAllCounterParts) {
159 return result;
160 }
161 }
162 }
163 }
164
165 return result;
166 }
167
168 @Override
169 public void deliverAll(final MessageSelector kind) {
170 if (kind == PhasedSelector.ANTI_MONOTONE) {
171 // use the buffer during delivering so that there is a clear
172 // separation between the stages
173 this.deliveringAntiMonotone = true;
174
175 for (final Tuple group : this.antiMonotoneQueue.getGroups()) {
176 for (final Entry<Tuple, Integer> entry : this.antiMonotoneQueue.getTuplesByGroup(group).entrySet()) {
177 final Tuple update = entry.getKey();
178 final int count = entry.getValue();
179 assert count < 0;
180 for (int i = 0; i < Math.abs(count); i++) {
181 this.receiver.updateWithPosetInfo(Direction.DELETE, update, false);
182 }
183 }
184 }
185
186 this.deliveringAntiMonotone = false;
187 swapAndClearAntiMonotone();
188 } else if (kind == PhasedSelector.MONOTONE) {
189 // use the buffer during delivering so that there is a clear
190 // separation between the stages
191 this.deliveringMonotone = true;
192
193 for (final Tuple group : this.monotoneQueue.getGroups()) {
194 for (final Entry<Tuple, Integer> entry : this.monotoneQueue.getTuplesByGroup(group).entrySet()) {
195 final Tuple update = entry.getKey();
196 final int count = entry.getValue();
197 assert count != 0;
198 final Direction direction = count < 0 ? Direction.DELETE : Direction.INSERT;
199 for (int i = 0; i < Math.abs(count); i++) {
200 this.receiver.updateWithPosetInfo(direction, update, true);
201 }
202 }
203 }
204
205 this.deliveringMonotone = false;
206 swapAndClearMonotone();
207 } else {
208 throw new IllegalArgumentException("Unsupported message kind " + kind);
209 }
210 }
211
212 @Override
213 public String toString() {
214 return "PA_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " "
215 + this.getActiveAntiMonotoneQueue();
216 }
217
218}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java
new file mode 100644
index 00000000..afa155b2
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java
@@ -0,0 +1,135 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timeless;
10
11import java.util.Map.Entry;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.util.Direction;
15import tools.refinery.viatra.runtime.rete.network.Receiver;
16import tools.refinery.viatra.runtime.rete.network.ReteContainer;
17import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
18import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
19import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21import tools.refinery.viatra.runtime.rete.network.indexer.DefaultMessageIndexer;
22import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox;
23import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
24
25/**
26 * A mailbox implementation that splits updates messages according to the standard subset ordering into anti-monotonic
27 * (deletions) and monotonic (insertions) updates.
28 *
29 * @author Tamas Szabo
30 * @since 2.0
31 */
32public class UpdateSplittingMailbox extends AbstractUpdateSplittingMailbox<DefaultMessageIndexer, Receiver>
33 implements AdaptableMailbox {
34
35 protected Mailbox adapter;
36
37 public UpdateSplittingMailbox(final Receiver receiver, final ReteContainer container) {
38 super(receiver, container, DefaultMessageIndexer::new);
39 this.adapter = this;
40 }
41
42 @Override
43 public Mailbox getAdapter() {
44 return this.adapter;
45 }
46
47 @Override
48 public void setAdapter(final Mailbox adapter) {
49 this.adapter = adapter;
50 }
51
52 @Override
53 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
54 final DefaultMessageIndexer monotoneQueue = getActiveMonotoneQueue();
55 final DefaultMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue();
56 final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0;
57 final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0;
58
59 // it cannot happen that it was present in both
60 assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone);
61
62 if (direction == Direction.INSERT) {
63 if (wasPresentAsAntiMonotone) {
64 // it was an anti-monotone one before
65 antiMonotoneQueue.insert(update);
66 } else {
67 // it was a monotone one before or did not exist at all
68 monotoneQueue.insert(update);
69 }
70 } else {
71 if (wasPresentAsMonotone) {
72 // it was a monotone one before
73 monotoneQueue.delete(update);
74 } else {
75 // it was an anti-monotone one before or did not exist at all
76 antiMonotoneQueue.delete(update);
77 }
78 }
79
80 final Mailbox targetMailbox = this.adapter;
81 final CommunicationGroup targetGroup = this.adapter.getCurrentGroup();
82
83 if (antiMonotoneQueue.isEmpty()) {
84 targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.ANTI_MONOTONE);
85 } else {
86 targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.ANTI_MONOTONE);
87 }
88
89 if (monotoneQueue.isEmpty()) {
90 targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.MONOTONE);
91 } else {
92 targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.MONOTONE);
93 }
94 }
95
96 @Override
97 public void deliverAll(final MessageSelector kind) {
98 if (kind == PhasedSelector.ANTI_MONOTONE) {
99 // deliver anti-monotone
100 this.deliveringAntiMonotone = true;
101 for (final Entry<Tuple, Integer> entry : this.antiMonotoneQueue.getTuples().entrySet()) {
102 final Tuple update = entry.getKey();
103 final int count = entry.getValue();
104 assert count < 0;
105 for (int i = 0; i < Math.abs(count); i++) {
106 this.receiver.update(Direction.DELETE, update, Timestamp.ZERO);
107 }
108 }
109 this.deliveringAntiMonotone = false;
110 swapAndClearAntiMonotone();
111 } else if (kind == PhasedSelector.MONOTONE) {
112 // deliver monotone
113 this.deliveringMonotone = true;
114 for (final Entry<Tuple, Integer> entry : this.monotoneQueue.getTuples().entrySet()) {
115 final Tuple update = entry.getKey();
116 final int count = entry.getValue();
117 assert count > 0;
118 for (int i = 0; i < count; i++) {
119 this.receiver.update(Direction.INSERT, update, Timestamp.ZERO);
120 }
121 }
122 this.deliveringMonotone = false;
123 swapAndClearMonotone();
124 } else {
125 throw new IllegalArgumentException("Unsupported message kind " + kind);
126 }
127 }
128
129 @Override
130 public String toString() {
131 return "US_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " "
132 + this.getActiveAntiMonotoneQueue();
133 }
134
135}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java
new file mode 100644
index 00000000..bf3b8e14
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java
@@ -0,0 +1,150 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.network.mailbox.timely;
10
11import java.util.Map;
12import java.util.TreeMap;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
18import tools.refinery.viatra.runtime.rete.network.Receiver;
19import tools.refinery.viatra.runtime.rete.network.ReteContainer;
20import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
21import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector;
22import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
23import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
24import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
25
26public class TimelyMailbox implements Mailbox {
27
28 protected TreeMap<Timestamp, Map<Tuple, Integer>> queue;
29 protected final Receiver receiver;
30 protected final ReteContainer container;
31 protected CommunicationGroup group;
32 protected boolean fallThrough;
33
34 public TimelyMailbox(final Receiver receiver, final ReteContainer container) {
35 this.receiver = receiver;
36 this.container = container;
37 this.queue = CollectionsFactory.createTreeMap();
38 }
39
40 protected TreeMap<Timestamp, Map<Tuple, Integer>> getActiveQueue() {
41 return this.queue;
42 }
43
44 @Override
45 public boolean isEmpty() {
46 return getActiveQueue().isEmpty();
47 }
48
49 @Override
50 public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) {
51 final TreeMap<Timestamp, Map<Tuple, Integer>> activeQueue = getActiveQueue();
52
53 Map<Tuple, Integer> tupleMap = activeQueue.get(timestamp);
54 final boolean wasEmpty = tupleMap == null;
55 boolean significantChange = false;
56
57 if (tupleMap == null) {
58 tupleMap = CollectionsFactory.createMap();
59 activeQueue.put(timestamp, tupleMap);
60 significantChange = true;
61 }
62
63 Integer count = tupleMap.get(update);
64 if (count == null) {
65 count = 0;
66 significantChange = true;
67 }
68
69 if (direction == Direction.DELETE) {
70 count--;
71 } else {
72 count++;
73 }
74
75 if (count == 0) {
76 tupleMap.remove(update);
77 if (tupleMap.isEmpty()) {
78 activeQueue.remove(timestamp);
79 }
80 significantChange = true;
81 } else {
82 tupleMap.put(update, count);
83 }
84
85 if (significantChange) {
86 if (wasEmpty) {
87 this.group.notifyHasMessage(this, timestamp);
88 } else if (tupleMap.isEmpty()) {
89 final Timestamp resumableTimestamp = (this.receiver instanceof ResumableNode)
90 ? ((ResumableNode) this.receiver).getResumableTimestamp()
91 : null;
92 // check if there is folding left to do before unsubscribing just based on the message queue being empty
93 if (resumableTimestamp == null || resumableTimestamp.compareTo(timestamp) != 0) {
94 this.group.notifyLostAllMessages(this, timestamp);
95 }
96 }
97 }
98 }
99
100 @Override
101 public void deliverAll(final MessageSelector selector) {
102 if (selector instanceof Timestamp) {
103 final Timestamp timestamp = (Timestamp) selector;
104 // REMOVE the tuples associated with the selector, dont just query them
105 final Map<Tuple, Integer> tupleMap = this.queue.remove(timestamp);
106
107 // tupleMap may be empty if we only have lazy folding to do
108 if (tupleMap != null) {
109 this.receiver.batchUpdate(tupleMap.entrySet(), timestamp);
110 }
111
112 if (this.container.getTimelyConfiguration()
113 .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) {
114 // (1) either normal delivery, which ended up being a lazy folding state
115 // (2) and/or lazy folding needs to be resumed
116 if (this.receiver instanceof ResumableNode) {
117 ((ResumableNode) this.receiver).resumeAt(timestamp);
118 }
119 }
120 } else {
121 throw new IllegalArgumentException("Unsupported message selector " + selector);
122 }
123 }
124
125 @Override
126 public String toString() {
127 return "DDF_MBOX (" + this.receiver + ") " + this.getActiveQueue();
128 }
129
130 @Override
131 public Receiver getReceiver() {
132 return this.receiver;
133 }
134
135 @Override
136 public void clear() {
137 this.queue.clear();
138 }
139
140 @Override
141 public CommunicationGroup getCurrentGroup() {
142 return this.group;
143 }
144
145 @Override
146 public void setCurrentGroup(final CommunicationGroup group) {
147 this.group = group;
148 }
149
150}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java
new file mode 100644
index 00000000..2fed3225
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java
@@ -0,0 +1,125 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.remote;
11
12import tools.refinery.viatra.runtime.rete.network.Node;
13import tools.refinery.viatra.runtime.rete.network.ReteContainer;
14
15/**
16 * Remote identifier of a node of type T.
17 *
18 * @author Gabor Bergmann
19 *
20 */
21public class Address<T extends Node> {
22 ReteContainer container;
23 Long nodeId;
24 /**
25 * Feel free to leave null e.g. if node is in a separate JVM.
26 */
27 T nodeCache;
28
29 /**
30 * Address of local node (use only for containers in the same VM!)
31 */
32 public static <N extends Node> Address<N> of(N node) {
33 return new Address<N>(node);
34 }
35
36 /**
37 * General constructor.
38 *
39 * @param container
40 * @param nodeId
41 */
42 public Address(ReteContainer container, Long nodeId) {
43 super();
44 this.container = container;
45 this.nodeId = nodeId;
46 }
47
48 /**
49 * Local-only constructor. (use only for containers in the same VM!)
50 *
51 * @param node
52 * the node to address
53 */
54 public Address(T node) {
55 super();
56 this.nodeCache = node;
57 this.container = node.getContainer();
58 this.nodeId = node.getNodeId();
59 }
60
61 @Override
62 public int hashCode() {
63 final int prime = 31;
64 int result = 1;
65 result = prime * result + ((container == null) ? 0 : container.hashCode());
66 result = prime * result + ((nodeId == null) ? 0 : nodeId.hashCode());
67 return result;
68 }
69
70 @Override
71 public boolean equals(Object obj) {
72 if (this == obj)
73 return true;
74 if (obj == null)
75 return false;
76 if (!(obj instanceof Address<?>))
77 return false;
78 final Address<?> other = (Address<?>) obj;
79 if (container == null) {
80 if (other.container != null)
81 return false;
82 } else if (!container.equals(other.container))
83 return false;
84 if (nodeId == null) {
85 if (other.nodeId != null)
86 return false;
87 } else if (!nodeId.equals(other.nodeId))
88 return false;
89 return true;
90 }
91
92 public ReteContainer getContainer() {
93 return container;
94 }
95
96 public void setContainer(ReteContainer container) {
97 this.container = container;
98 }
99
100 public Long getNodeId() {
101 return nodeId;
102 }
103
104 public void setNodeId(Long nodeId) {
105 this.nodeId = nodeId;
106 }
107
108 public T getNodeCache() {
109 return nodeCache;
110 }
111
112 public void setNodeCache(T nodeCache) {
113 this.nodeCache = nodeCache;
114 }
115
116 @Override
117 public String toString() {
118 if (nodeCache == null)
119 return "A(" + nodeId + " @ " + container + ")";
120 else
121 return "A(" + nodeCache + ")";
122
123 }
124
125}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java
new file mode 100644
index 00000000..f7d267af
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java
@@ -0,0 +1,63 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.remote;
11
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
20import tools.refinery.viatra.runtime.rete.network.Receiver;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
23import tools.refinery.viatra.runtime.rete.single.SingleInputNode;
24
25/**
26 * This node delivers updates to a remote recipient; no updates are propagated further in this network.
27 *
28 * @author Gabor Bergmann
29 *
30 */
31public class RemoteReceiver extends SingleInputNode {
32
33 List<Address<? extends Receiver>> targets;
34
35 public RemoteReceiver(ReteContainer reteContainer) {
36 super(reteContainer);
37 targets = new ArrayList<Address<? extends Receiver>>();
38 }
39
40 public void addTarget(Address<? extends Receiver> target) {
41 targets.add(target);
42 }
43
44 @Override
45 public void pullInto(Collection<Tuple> collector, boolean flush) {
46 propagatePullInto(collector, flush);
47 }
48
49 @Override
50 public void pullIntoWithTimeline(Map<Tuple, Timeline<Timestamp>> collector, boolean flush) {
51 throw new UnsupportedOperationException();
52 }
53
54 public Collection<Tuple> remotePull(boolean flush) {
55 return reteContainer.pullContents(this, flush);
56 }
57
58 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
59 for (Address<? extends Receiver> ad : targets)
60 reteContainer.sendUpdateToRemoteAddress(ad, direction, updateElement);
61 }
62
63}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java
new file mode 100644
index 00000000..cbe4d177
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.remote;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20import tools.refinery.viatra.runtime.rete.single.SingleInputNode;
21
22/**
23 * This node receives updates from a remote supplier; no local updates are expected.
24 *
25 * @author Gabor Bergmann
26 *
27 */
28public class RemoteSupplier extends SingleInputNode {
29
30 RemoteReceiver counterpart;
31
32 public RemoteSupplier(ReteContainer reteContainer, RemoteReceiver counterpart) {
33 super(reteContainer);
34 this.counterpart = counterpart;
35 counterpart.addTarget(reteContainer.makeAddress(this));
36 }
37
38 @Override
39 public void pullInto(Collection<Tuple> collector, boolean flush) {
40 Collection<Tuple> pulled = counterpart.remotePull(flush);
41 collector.addAll(pulled);
42 }
43
44 @Override
45 public void pullIntoWithTimeline(Map<Tuple, Timeline<Timestamp>> collector, boolean flush) {
46 throw new UnsupportedOperationException();
47 }
48
49 @Override
50 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
51 propagateUpdate(direction, updateElement, timestamp);
52 }
53
54}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java
new file mode 100644
index 00000000..e92ce63f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java
@@ -0,0 +1,138 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.List;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
20import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription;
21import tools.refinery.viatra.runtime.rete.network.ReteContainer;
22import tools.refinery.viatra.runtime.rete.network.StandardNode;
23import tools.refinery.viatra.runtime.rete.network.Supplier;
24import tools.refinery.viatra.runtime.rete.network.Tunnel;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
27import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
28import tools.refinery.viatra.runtime.rete.util.Options;
29
30/**
31 * Ensures that no identical copies get to the output. Only one replica of each pattern substitution may traverse this
32 * node. There are both timeless and timely implementations.
33 *
34 * @author Gabor Bergmann
35 * @author Tamas Szabo
36 * @noinstantiate This class is not intended to be instantiated by clients.
37 * @noextend This class is not intended to be subclassed by clients.
38 * @since 2.2
39 */
40public abstract class AbstractUniquenessEnforcerNode extends StandardNode implements Tunnel {
41
42 protected final Collection<Supplier> parents;
43 protected ProjectionIndexer memoryNullIndexer;
44 protected ProjectionIndexer memoryIdentityIndexer;
45 protected final int tupleWidth;
46 // MUST BE INSTANTIATED IN THE CONCRETE SUBCLASSES AFTER ALL FIELDS ARE SET
47 protected Mailbox mailbox;
48 protected final TupleMask nullMask;
49 protected final TupleMask identityMask;
50 protected final List<ListenerSubscription> specializedListeners;
51
52 public AbstractUniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) {
53 super(reteContainer);
54 this.parents = new ArrayList<Supplier>();
55 this.specializedListeners = new ArrayList<ListenerSubscription>();
56 this.tupleWidth = tupleWidth;
57 this.nullMask = TupleMask.linear(0, tupleWidth);
58 this.identityMask = TupleMask.identity(tupleWidth);
59 }
60
61 protected abstract Mailbox instantiateMailbox();
62
63 @Override
64 public Mailbox getMailbox() {
65 return this.mailbox;
66 }
67
68 /**
69 * @since 2.8
70 */
71 public abstract Set<Tuple> getTuples();
72
73 /**
74 * @since 2.4
75 */
76 protected void propagate(final Direction direction, final Tuple update, final Timestamp timestamp) {
77 // See Bug 518434
78 // trivial (non-active) indexers must be updated before other listeners
79 // so that if they are joined against each other, trivial indexers lookups
80 // will be consistent with their notifications;
81 // also, their subscriptions must share a single order
82 for (final ListenerSubscription subscription : specializedListeners) {
83 subscription.propagate(direction, update, timestamp);
84 }
85 propagateUpdate(direction, update, timestamp);
86 }
87
88 @Override
89 public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) {
90 if (Options.employTrivialIndexers) {
91 if (nullMask.equals(mask)) {
92 final ProjectionIndexer indexer = getNullIndexer();
93 for (final TraceInfo traceInfo : traces) {
94 indexer.assignTraceInfo(traceInfo);
95 }
96 return indexer;
97 }
98 if (identityMask.equals(mask)) {
99 final ProjectionIndexer indexer = getIdentityIndexer();
100 for (final TraceInfo traceInfo : traces) {
101 indexer.assignTraceInfo(traceInfo);
102 }
103 return indexer;
104 }
105 }
106 return super.constructIndex(mask, traces);
107 }
108
109 public abstract ProjectionIndexer getNullIndexer();
110
111 public abstract ProjectionIndexer getIdentityIndexer();
112
113 @Override
114 public void appendParent(final Supplier supplier) {
115 parents.add(supplier);
116 }
117
118 @Override
119 public void removeParent(final Supplier supplier) {
120 parents.remove(supplier);
121 }
122
123 @Override
124 public Collection<Supplier> getParents() {
125 return parents;
126 }
127
128 @Override
129 public void assignTraceInfo(final TraceInfo traceInfo) {
130 super.assignTraceInfo(traceInfo);
131 if (traceInfo.propagateFromStandardNodeToSupplierParent()) {
132 for (final Supplier parent : parents) {
133 parent.acceptPropagatedTraceInfo(traceInfo);
134 }
135 }
136 }
137
138} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java
new file mode 100644
index 00000000..c68036b5
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.util.Direction;
14import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver;
15import tools.refinery.viatra.runtime.rete.network.ReteContainer;
16import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
17
18/**
19 * @author Bergmann Gabor
20 *
21 */
22public class CallbackNode extends SimpleReceiver {
23
24 IUpdateable updateable;
25
26 public CallbackNode(ReteContainer reteContainer, IUpdateable updateable)
27 {
28 super(reteContainer);
29 this.updateable = updateable;
30 }
31
32 @Override
33 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
34 updateable.update(updateElement, direction == Direction.INSERT);
35 }
36
37}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java
new file mode 100644
index 00000000..eca8bc17
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java
@@ -0,0 +1,79 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Iterator;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.rete.network.ProductionNode;
19import tools.refinery.viatra.runtime.rete.network.ReteContainer;
20import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery;
21import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
22
23/**
24 * Default implementation of the Production node, based on UniquenessEnforcerNode
25 *
26 * @author Gabor Bergmann
27 * @noinstantiate This class is not intended to be instantiated by clients.
28 */
29public class DefaultProductionNode extends UniquenessEnforcerNode implements ProductionNode {
30
31 protected final Map<String, Integer> posMapping;
32
33 /**
34 * @since 1.6
35 */
36 public DefaultProductionNode(final ReteContainer reteContainer, final Map<String, Integer> posMapping,
37 final boolean deleteRederiveEvaluation) {
38 this(reteContainer, posMapping, deleteRederiveEvaluation, null, null, null);
39 }
40
41 /**
42 * @since 1.6
43 */
44 public DefaultProductionNode(final ReteContainer reteContainer, final Map<String, Integer> posMapping,
45 final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask,
46 final IPosetComparator posetComparator) {
47 super(reteContainer, posMapping.size(), deleteRederiveEvaluation, coreMask, posetMask, posetComparator);
48 this.posMapping = posMapping;
49 }
50
51 @Override
52 public Map<String, Integer> getPosMapping() {
53 return posMapping;
54 }
55
56 @Override
57 public Iterator<Tuple> iterator() {
58 return memory.iterator();
59 }
60
61 @Override
62 public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) {
63 if (traceInfo.propagateToProductionNodeParentAlso()) {
64 super.acceptPropagatedTraceInfo(traceInfo);
65 }
66 }
67
68 @Override
69 public String toString() {
70 for (final TraceInfo traceInfo : this.traceInfos) {
71 if (traceInfo instanceof CompiledQuery) {
72 final String patternName = ((CompiledQuery) traceInfo).getPatternName();
73 return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString());
74 }
75 }
76 return super.toString();
77 }
78
79}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java
new file mode 100644
index 00000000..803bab20
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import java.util.Collection;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.Supplier;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22/**
23 * A bucket holds a filtered set of tuples of its parent {@link DiscriminatorDispatcherNode}.
24 * Exactly those that have the given bucket key at their discrimination column.
25 *
26 * <p> During operation, tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)}
27 *
28 * @author Gabor Bergmann
29 * @since 1.5
30 */
31public class DiscriminatorBucketNode extends SingleInputNode {
32
33 private Object bucketKey;
34
35 /**
36 * @param bucketKey will be wrapped using {@link IQueryRuntimeContext#wrapElement(Object)}
37
38 */
39 public DiscriminatorBucketNode(ReteContainer reteContainer, Object bucketKey) {
40 super(reteContainer);
41 this.bucketKey = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapElement(bucketKey);
42 }
43
44 @Override
45 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
46 if (parent != null) {
47 getDispatcher().pullIntoFiltered(collector, bucketKey, flush);
48 }
49 }
50
51 @Override
52 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
53 if (parent != null) {
54 getDispatcher().pullIntoWithTimestampFiltered(collector, bucketKey, flush);
55 }
56 }
57
58 @Override
59 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
60 propagateUpdate(direction, updateElement, timestamp);
61 }
62
63 public Object getBucketKey() {
64 return bucketKey;
65 }
66
67 @Override
68 public void appendParent(Supplier supplier) {
69 if (! (supplier instanceof DiscriminatorDispatcherNode))
70 throw new IllegalArgumentException();
71 super.appendParent(supplier);
72 }
73
74 public DiscriminatorDispatcherNode getDispatcher() {
75 return (DiscriminatorDispatcherNode) parent;
76 }
77
78 @Override
79 protected String toStringCore() {
80 return String.format("%s<%s=='%s'>",
81 super.toStringCore(),
82 (getDispatcher() == null) ? "?" : getDispatcher().getDiscriminationColumnIndex(),
83 bucketKey);
84 }
85}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java
new file mode 100644
index 00000000..a8e11fcd
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java
@@ -0,0 +1,154 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashMap;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.matchers.util.Direction;
21import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
22import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
23import tools.refinery.viatra.runtime.rete.network.Receiver;
24import tools.refinery.viatra.runtime.rete.network.ReteContainer;
25import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
26import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
27
28/**
29 * Node that sends tuples off to different buckets (attached as children of type {@link DiscriminatorBucketNode}), based
30 * on the value of a given column.
31 *
32 * <p>
33 * Tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)}
34 *
35 * @author Gabor Bergmann
36 * @since 1.5
37 */
38public class DiscriminatorDispatcherNode extends SingleInputNode implements NetworkStructureChangeSensitiveNode {
39
40 private int discriminationColumnIndex;
41 private Map<Object, DiscriminatorBucketNode> buckets = new HashMap<>();
42 private Map<Object, Mailbox> bucketMailboxes = new HashMap<>();
43
44 /**
45 * @param reteContainer
46 */
47 public DiscriminatorDispatcherNode(ReteContainer reteContainer, int discriminationColumnIndex) {
48 super(reteContainer);
49 this.discriminationColumnIndex = discriminationColumnIndex;
50 }
51
52 @Override
53 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
54 Object dispatchKey = updateElement.get(discriminationColumnIndex);
55 Mailbox bucketMailBox = bucketMailboxes.get(dispatchKey);
56 if (bucketMailBox != null) {
57 bucketMailBox.postMessage(direction, updateElement, timestamp);
58 }
59 }
60
61 public int getDiscriminationColumnIndex() {
62 return discriminationColumnIndex;
63 }
64
65 @Override
66 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
67 propagatePullInto(collector, flush);
68 }
69
70 @Override
71 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
72 propagatePullIntoWithTimestamp(collector, flush);
73 }
74
75 /**
76 * @since 2.3
77 */
78 public void pullIntoFiltered(final Collection<Tuple> collector, final Object bucketKey, final boolean flush) {
79 final ArrayList<Tuple> unfiltered = new ArrayList<Tuple>();
80 propagatePullInto(unfiltered, flush);
81 for (Tuple tuple : unfiltered) {
82 if (bucketKey.equals(tuple.get(discriminationColumnIndex))) {
83 collector.add(tuple);
84 }
85 }
86 }
87
88 /**
89 * @since 2.3
90 */
91 public void pullIntoWithTimestampFiltered(final Map<Tuple, Timeline<Timestamp>> collector, final Object bucketKey,
92 final boolean flush) {
93 final Map<Tuple, Timeline<Timestamp>> unfiltered = CollectionsFactory.createMap();
94 propagatePullIntoWithTimestamp(unfiltered, flush);
95 for (final Entry<Tuple, Timeline<Timestamp>> entry : unfiltered.entrySet()) {
96 if (bucketKey.equals(entry.getKey().get(discriminationColumnIndex))) {
97 collector.put(entry.getKey(), entry.getValue());
98 }
99 }
100 }
101
102 @Override
103 public void appendChild(Receiver receiver) {
104 super.appendChild(receiver);
105 if (receiver instanceof DiscriminatorBucketNode) {
106 DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver;
107 Object bucketKey = bucket.getBucketKey();
108 DiscriminatorBucketNode old = buckets.put(bucketKey, bucket);
109 if (old != null) {
110 throw new IllegalStateException();
111 }
112 bucketMailboxes.put(bucketKey, this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox()));
113 }
114 }
115
116 /**
117 * @since 2.2
118 */
119 public Map<Object, Mailbox> getBucketMailboxes() {
120 return this.bucketMailboxes;
121 }
122
123 @Override
124 public void networkStructureChanged() {
125 bucketMailboxes.clear();
126 for (Receiver receiver : children) {
127 if (receiver instanceof DiscriminatorBucketNode) {
128 DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver;
129 Object bucketKey = bucket.getBucketKey();
130 bucketMailboxes.put(bucketKey,
131 this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox()));
132 }
133 }
134 }
135
136 @Override
137 public void removeChild(Receiver receiver) {
138 super.removeChild(receiver);
139 if (receiver instanceof DiscriminatorBucketNode) {
140 DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver;
141 Object bucketKey = bucket.getBucketKey();
142 DiscriminatorBucketNode old = buckets.remove(bucketKey);
143 if (old != bucket)
144 throw new IllegalStateException();
145 bucketMailboxes.remove(bucketKey);
146 }
147 }
148
149 @Override
150 protected String toStringCore() {
151 return super.toStringCore() + '<' + discriminationColumnIndex + '>';
152 }
153
154}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java
new file mode 100644
index 00000000..014c2016
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.rete.network.ReteContainer;
14
15public class EqualityFilterNode extends FilterNode {
16
17 int[] indices;
18 int first;
19
20 /**
21 * @param reteContainer
22 * @param indices
23 * indices of the Tuple that should hold equal values
24 */
25 public EqualityFilterNode(ReteContainer reteContainer, int[] indices) {
26 super(reteContainer);
27 this.indices = indices;
28 first = indices[0];
29 }
30
31 @Override
32 public boolean check(Tuple ps) {
33 Object firstElement = ps.get(first);
34 for (int i = 1 /* first is omitted */; i < indices.length; i++) {
35 if (!ps.get(indices[i]).equals(firstElement))
36 return false;
37 }
38 return true;
39 }
40
41}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java
new file mode 100644
index 00000000..f66f1715
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java
@@ -0,0 +1,69 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.network.ReteContainer;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22/**
23 * This node implements a simple filter. A stateless abstract check() predicate determines whether a matching is allowed
24 * to pass.
25 *
26 * @author Gabor Bergmann
27 *
28 */
29public abstract class FilterNode extends SingleInputNode {
30
31 public FilterNode(final ReteContainer reteContainer) {
32 super(reteContainer);
33 }
34
35 /**
36 * Abstract filtering predicate. Expected to be stateless.
37 *
38 * @param ps
39 * the matching to be checked.
40 * @return true if and only if the parameter matching is allowed to pass through this node.
41 */
42 public abstract boolean check(final Tuple ps);
43
44 @Override
45 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
46 for (final Tuple ps : this.reteContainer.pullPropagatedContents(this, flush)) {
47 if (check(ps)) {
48 collector.add(ps);
49 }
50 }
51 }
52
53 @Override
54 public void pullIntoWithTimeline(Map<Tuple, Timeline<Timestamp>> collector, boolean flush) {
55 for (final Entry<Tuple, Timeline<Timestamp>> entry : this.reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) {
56 if (check(entry.getKey())) {
57 collector.put(entry.getKey(), entry.getValue());
58 }
59 }
60 }
61
62 @Override
63 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
64 if (check(updateElement)) {
65 propagateUpdate(direction, updateElement, timestamp);
66 }
67 }
68
69}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java
new file mode 100644
index 00000000..8dd3e949
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java
@@ -0,0 +1,52 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
14import tools.refinery.viatra.runtime.rete.network.ReteContainer;
15
16/**
17 * This node filters patterns according to equalities and inequalities of elements. The 'subject' element is asserted to
18 * be different from the elements given by the inequalityMask.
19 *
20 *
21 * @author Gabor Bergmann
22 *
23 */
24public class InequalityFilterNode extends FilterNode {
25
26 int subjectIndex;
27 TupleMask inequalityMask;
28
29 /**
30 * @param reteContainer
31 * @param subject
32 * the index of the element that should be compared.
33 * @param inequalityMask
34 * the indices of elements that should be different from the subjectIndex.
35 */
36 public InequalityFilterNode(ReteContainer reteContainer, int subject, TupleMask inequalityMask) {
37 super(reteContainer);
38 this.subjectIndex = subject;
39 this.inequalityMask = inequalityMask;
40 }
41
42 @Override
43 public boolean check(Tuple ps) {
44 Object subject = ps.get(subjectIndex);
45 for (int ineq : inequalityMask.indices) {
46 if (subject.equals(ps.get(ineq)))
47 return false;
48 }
49 return true;
50 }
51
52}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java
new file mode 100644
index 00000000..95018c4f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java
@@ -0,0 +1,125 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import tools.refinery.viatra.runtime.rete.itc.alg.representative.RepresentativeElectionAlgorithm;
12import tools.refinery.viatra.runtime.rete.itc.alg.representative.RepresentativeObserver;
13import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
16import tools.refinery.viatra.runtime.matchers.util.Clearable;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.network.ReinitializedNode;
20import tools.refinery.viatra.runtime.rete.network.ReteContainer;
21import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
22
23import java.util.Collection;
24import java.util.Map;
25
26public class RepresentativeElectionNode extends SingleInputNode implements Clearable, RepresentativeObserver,
27 ReinitializedNode {
28 private final RepresentativeElectionAlgorithm.Factory algorithmFactory;
29 private Graph<Object> graph;
30 private RepresentativeElectionAlgorithm algorithm;
31
32 public RepresentativeElectionNode(ReteContainer reteContainer,
33 RepresentativeElectionAlgorithm.Factory algorithmFactory) {
34 super(reteContainer);
35 this.algorithmFactory = algorithmFactory;
36 graph = new Graph<>();
37 algorithm = algorithmFactory.create(graph);
38 algorithm.setObserver(this);
39 reteContainer.registerClearable(this);
40 }
41
42 @Override
43 public void networkStructureChanged() {
44 if (reteContainer.isTimelyEvaluation() && reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
45 throw new IllegalStateException(this + " cannot be used in recursive differential dataflow evaluation!");
46 }
47 super.networkStructureChanged();
48 }
49
50 @Override
51 public void reinitializeWith(Collection<Tuple> tuples) {
52 algorithm.dispose();
53 graph = new Graph<>();
54 for (var tuple : tuples) {
55 insertEdge(tuple.get(0), tuple.get(1));
56 }
57 algorithm = algorithmFactory.create(graph);
58 algorithm.setObserver(this);
59 }
60
61 @Override
62 public void tupleChanged(Object source, Object representative, Direction direction) {
63 var tuple = Tuples.staticArityFlatTupleOf(source, representative);
64 propagateUpdate(direction, tuple, Timestamp.ZERO);
65 }
66
67 @Override
68 public void clear() {
69 algorithm.dispose();
70 graph = new Graph<>();
71 algorithm = algorithmFactory.create(graph);
72 }
73
74 @Override
75 public void update(Direction direction, Tuple updateElement, Timestamp timestamp) {
76 var source = updateElement.get(0);
77 var target = updateElement.get(1);
78 switch (direction) {
79 case INSERT -> insertEdge(source, target);
80 case DELETE -> deleteEdge(source, target);
81 default -> throw new IllegalArgumentException("Unknown direction: " + direction);
82 }
83 }
84
85 private void insertEdge(Object source, Object target) {
86 graph.insertNode(source);
87 graph.insertNode(target);
88 graph.insertEdge(source, target);
89 }
90
91 private void deleteEdge(Object source, Object target) {
92 graph.deleteEdgeIfExists(source, target);
93 if (isIsolated(source)) {
94 graph.deleteNode(source);
95 }
96 if (!source.equals(target) && isIsolated(target)) {
97 graph.deleteNode(target);
98 }
99 }
100
101 private boolean isIsolated(Object node) {
102 return graph.getTargetNodes(node).isEmpty() && graph.getSourceNodes(node).isEmpty();
103 }
104
105 @Override
106 public void pullInto(Collection<Tuple> collector, boolean flush) {
107 for (var entry : algorithm.getComponents().entrySet()) {
108 var representative = entry.getKey();
109 for (var node : entry.getValue()) {
110 collector.add(Tuples.staticArityFlatTupleOf(node, representative));
111 }
112 }
113 }
114
115 @Override
116 public void pullIntoWithTimeline(Map<Tuple, Timeline<Timestamp>> collector, boolean flush) {
117 // Use all zero timestamps because this node cannot be used in recursive groups anyway.
118 for (var entry : algorithm.getComponents().entrySet()) {
119 var representative = entry.getKey();
120 for (var node : entry.getValue()) {
121 collector.put(Tuples.staticArityFlatTupleOf(node, representative), Timestamp.INSERT_AT_ZERO_TIMELINE);
122 }
123 }
124 }
125}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java
new file mode 100644
index 00000000..99fc45b2
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Map;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.StandardNode;
20import tools.refinery.viatra.runtime.rete.network.Supplier;
21import tools.refinery.viatra.runtime.rete.network.Tunnel;
22import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker;
23import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
24import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
25import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
26import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
27import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
28
29/**
30 * @author Gabor Bergmann
31 *
32 */
33public abstract class SingleInputNode extends StandardNode implements Tunnel {
34
35 protected Supplier parent;
36 /**
37 * @since 1.6
38 */
39 protected Mailbox mailbox;
40
41 public SingleInputNode(ReteContainer reteContainer) {
42 super(reteContainer);
43 mailbox = instantiateMailbox();
44 reteContainer.registerClearable(mailbox);
45 parent = null;
46 }
47
48 /**
49 * Instantiates the {@link Mailbox} of this receiver.
50 * Subclasses may override this method to provide their own mailbox implementation.
51 *
52 * @return the mailbox
53 * @since 2.0
54 */
55 protected Mailbox instantiateMailbox() {
56 if (this.reteContainer.isTimelyEvaluation()) {
57 return new TimelyMailbox(this, this.reteContainer);
58 } else {
59 return new BehaviorChangingMailbox(this, this.reteContainer);
60 }
61 }
62
63 @Override
64 public CommunicationTracker getCommunicationTracker() {
65 return this.reteContainer.getCommunicationTracker();
66 }
67
68 @Override
69 public Mailbox getMailbox() {
70 return this.mailbox;
71 }
72
73 @Override
74 public void appendParent(Supplier supplier) {
75 if (parent == null)
76 parent = supplier;
77 else
78 throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent
79 + ") and cannot connect to additional parent (" + supplier
80 + ") as it is not a Uniqueness Enforcer Node. ");
81 }
82
83 @Override
84 public void removeParent(Supplier supplier) {
85 if (parent == supplier)
86 parent = null;
87 else
88 throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not "
89 + supplier);
90 }
91
92 /**
93 * To be called by derived classes and ReteContainer.
94 */
95 public void propagatePullInto(final Collection<Tuple> collector, final boolean flush) {
96 if (parent != null) {
97 parent.pullInto(collector, flush);
98 }
99 }
100
101 /**
102 * To be called by derived classes and ReteContainer.
103 */
104 public void propagatePullIntoWithTimestamp(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
105 if (parent != null) {
106 parent.pullIntoWithTimeline(collector, flush);
107 }
108 }
109
110 @Override
111 public Collection<Supplier> getParents() {
112 if (parent == null)
113 return Collections.emptySet();
114 else
115 return Collections.singleton(parent);
116 }
117
118 @Override
119 public void assignTraceInfo(TraceInfo traceInfo) {
120 super.assignTraceInfo(traceInfo);
121 if (traceInfo.propagateFromStandardNodeToSupplierParent())
122 if (parent != null)
123 parent.acceptPropagatedTraceInfo(traceInfo);
124 }
125
126}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java
new file mode 100644
index 00000000..82640948
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java
@@ -0,0 +1,63 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import java.util.Iterator;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15import tools.refinery.viatra.runtime.rete.network.ProductionNode;
16import tools.refinery.viatra.runtime.rete.network.ReteContainer;
17import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery;
18import tools.refinery.viatra.runtime.rete.traceability.TraceInfo;
19/**
20 * Differential dataflow implementation of the Production node, based on {@link TimelyUniquenessEnforcerNode}.
21 *
22 * @author Tamas Szabo
23 * @noinstantiate This class is not intended to be instantiated by clients.
24 * @since 2.3
25 */
26public class TimelyProductionNode extends TimelyUniquenessEnforcerNode implements ProductionNode {
27
28 protected final Map<String, Integer> posMapping;
29
30 public TimelyProductionNode(final ReteContainer reteContainer, final Map<String, Integer> posMapping) {
31 super(reteContainer, posMapping.size());
32 this.posMapping = posMapping;
33 }
34
35 @Override
36 public Map<String, Integer> getPosMapping() {
37 return this.posMapping;
38 }
39
40 @Override
41 public Iterator<Tuple> iterator() {
42 return this.memory.keySet().iterator();
43 }
44
45 @Override
46 public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) {
47 if (traceInfo.propagateToProductionNodeParentAlso()) {
48 super.acceptPropagatedTraceInfo(traceInfo);
49 }
50 }
51
52 @Override
53 public String toString() {
54 for (final TraceInfo traceInfo : this.traceInfos) {
55 if (traceInfo instanceof CompiledQuery) {
56 final String patternName = ((CompiledQuery) traceInfo).getPatternName();
57 return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString());
58 }
59 }
60 return super.toString();
61 }
62
63}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java
new file mode 100644
index 00000000..4c4b4fc0
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java
@@ -0,0 +1,161 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import java.util.Collection;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.matchers.util.Signed;
19import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
21import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
22import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
23import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryIdentityIndexer;
24import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryNullIndexer;
25import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation;
26import tools.refinery.viatra.runtime.rete.network.ReteContainer;
27import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
28import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
29import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode;
30import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
31import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox;
32
33/**
34 * Timely uniqueness enforcer node implementation.
35 *
36 * @author Tamas Szabo
37 * @noinstantiate This class is not intended to be instantiated by clients.
38 * @noextend This class is not intended to be subclassed by clients.
39 * @since 2.4
40 */
41public class TimelyUniquenessEnforcerNode extends AbstractUniquenessEnforcerNode implements ResumableNode {
42
43 protected final TimelyMemory<Timestamp> memory;
44 /**
45 * @since 2.4
46 */
47 protected CommunicationGroup group;
48
49 public TimelyUniquenessEnforcerNode(final ReteContainer container, final int tupleWidth) {
50 super(container, tupleWidth);
51 this.memory = new TimelyMemory<Timestamp>(
52 container.getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL);
53 container.registerClearable(this.memory);
54 this.mailbox = instantiateMailbox();
55 container.registerClearable(this.mailbox);
56 }
57
58 protected Mailbox instantiateMailbox() {
59 return new TimelyMailbox(this, this.reteContainer);
60 }
61
62 @Override
63 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
64 for (final Tuple tuple : this.memory.getTuplesAtInfinity()) {
65 collector.add(tuple);
66 }
67 }
68
69 @Override
70 public CommunicationGroup getCurrentGroup() {
71 return this.group;
72 }
73
74 @Override
75 public void setCurrentGroup(final CommunicationGroup group) {
76 this.group = group;
77 }
78
79 @Override
80 public Set<Tuple> getTuples() {
81 return this.memory.getTuplesAtInfinity();
82 }
83
84 /**
85 * @since 2.4
86 */
87 @Override
88 public Timestamp getResumableTimestamp() {
89 return this.memory.getResumableTimestamp();
90 }
91
92 /**
93 * @since 2.4
94 */
95 @Override
96 public void resumeAt(final Timestamp timestamp) {
97 final Map<Tuple, Diff<Timestamp>> diffMap = this.memory.resumeAt(timestamp);
98 for (final Entry<Tuple, Diff<Timestamp>> entry : diffMap.entrySet()) {
99 for (final Signed<Timestamp> signed : entry.getValue()) {
100 propagate(signed.getDirection(), entry.getKey(), signed.getPayload());
101 }
102 }
103 final Timestamp nextTimestamp = this.memory.getResumableTimestamp();
104 if (nextTimestamp != null) {
105 this.group.notifyHasMessage(this.mailbox, nextTimestamp);
106 }
107 }
108
109 @Override
110 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
111 Diff<Timestamp> resultDiff = null;
112 if (direction == Direction.INSERT) {
113 resultDiff = this.memory.put(update, timestamp);
114 } else {
115 try {
116 resultDiff = this.memory.remove(update, timestamp);
117 } catch (final IllegalStateException e) {
118 issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in "
119 + this.getClass().getName() + " " + this + " for pattern(s) "
120 + getTraceInfoPatternsEnumerated(), e);
121 // diff will remain unset in case of the exception, it is time to return
122 return;
123 }
124 }
125
126 for (final Signed<Timestamp> signed : resultDiff) {
127 propagate(signed.getDirection(), update, signed.getPayload());
128 }
129 }
130
131 @Override
132 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
133 collector.putAll(this.memory.asMap());
134 }
135
136 @Override
137 public ProjectionIndexer getNullIndexer() {
138 if (this.memoryNullIndexer == null) {
139 this.memoryNullIndexer = new TimelyMemoryNullIndexer(this.reteContainer, this.tupleWidth, this.memory, this,
140 this, this.specializedListeners);
141 this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer);
142 }
143 return this.memoryNullIndexer;
144 }
145
146 @Override
147 public ProjectionIndexer getIdentityIndexer() {
148 if (this.memoryIdentityIndexer == null) {
149 this.memoryIdentityIndexer = new TimelyMemoryIdentityIndexer(this.reteContainer, this.tupleWidth,
150 this.memory, this, this, this.specializedListeners);
151 this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer);
152 }
153 return this.memoryIdentityIndexer;
154 }
155
156 @Override
157 public void networkStructureChanged() {
158 super.networkStructureChanged();
159 }
160
161} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java
new file mode 100644
index 00000000..24750656
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.util.Direction;
18import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
19import tools.refinery.viatra.runtime.rete.network.ReteContainer;
20import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
21
22public abstract class TransformerNode extends SingleInputNode {
23
24 public TransformerNode(final ReteContainer reteContainer) {
25 super(reteContainer);
26 }
27
28 protected abstract Tuple transform(final Tuple input);
29
30 @Override
31 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
32 for (Tuple ps : reteContainer.pullPropagatedContents(this, flush)) {
33 collector.add(transform(ps));
34 }
35 }
36
37 @Override
38 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
39 for (final Entry<Tuple, Timeline<Timestamp>> entry : reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) {
40 collector.put(transform(entry.getKey()), entry.getValue());
41 }
42 }
43
44 @Override
45 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
46 propagateUpdate(direction, transform(updateElement), timestamp);
47 }
48
49}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java
new file mode 100644
index 00000000..fdda4ef4
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java
@@ -0,0 +1,147 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.single;
10
11import tools.refinery.viatra.runtime.rete.itc.alg.incscc.IncSCCAlg;
12import tools.refinery.viatra.runtime.rete.itc.alg.misc.Tuple;
13import tools.refinery.viatra.runtime.rete.itc.graphimpl.Graph;
14import tools.refinery.viatra.runtime.rete.itc.igraph.ITcDataSource;
15import tools.refinery.viatra.runtime.rete.itc.igraph.ITcObserver;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
17import tools.refinery.viatra.runtime.matchers.util.Clearable;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
20import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode;
21import tools.refinery.viatra.runtime.rete.network.ReinitializedNode;
22import tools.refinery.viatra.runtime.rete.network.ReteContainer;
23import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
24import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
25
26import java.util.Collection;
27import java.util.Map;
28
29/**
30 * This class represents a transitive closure node in the Rete net.
31 * <p>
32 * This node must not be used in recursive {@link CommunicationGroup}s.
33 *
34 * @author Gabor Bergmann
35 *
36 */
37public class TransitiveClosureNode extends SingleInputNode
38 implements Clearable, ITcObserver<Object>, NetworkStructureChangeSensitiveNode, ReinitializedNode {
39
40 private Graph<Object> graphDataSource;
41 private ITcDataSource<Object> transitiveClosureAlgorithm;
42
43 /**
44 * Create a new transitive closure rete node.
45 *
46 * Client may optionally call {@link #reinitializeWith(Collection)} before using the node, instead of inserting the
47 * initial set of tuples one by one.
48 *
49 * @param reteContainer
50 * the rete container of the node
51 */
52 public TransitiveClosureNode(ReteContainer reteContainer) {
53 super(reteContainer);
54 graphDataSource = new Graph<Object>();
55 transitiveClosureAlgorithm = new IncSCCAlg<Object>(graphDataSource);
56 transitiveClosureAlgorithm.attachObserver(this);
57 reteContainer.registerClearable(this);
58 }
59
60 @Override
61 public void networkStructureChanged() {
62 if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) {
63 throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!");
64 }
65 super.networkStructureChanged();
66 }
67
68 /**
69 * Initializes the graph data source with the given collection of tuples.
70 *
71 * @param tuples
72 * the initial collection of tuples
73 */
74 @Override
75 public void reinitializeWith(Collection<tools.refinery.viatra.runtime.matchers.tuple.Tuple> tuples) {
76 clear();
77
78 for (tools.refinery.viatra.runtime.matchers.tuple.Tuple t : tuples) {
79 graphDataSource.insertNode(t.get(0));
80 graphDataSource.insertNode(t.get(1));
81 graphDataSource.insertEdge(t.get(0), t.get(1));
82 }
83 transitiveClosureAlgorithm.attachObserver(this);
84 }
85
86 @Override
87 public void pullInto(final Collection<tools.refinery.viatra.runtime.matchers.tuple.Tuple> collector, final boolean flush) {
88 for (final Tuple<Object> tuple : ((IncSCCAlg<Object>) transitiveClosureAlgorithm).getTcRelation()) {
89 collector.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget()));
90 }
91 }
92
93 @Override
94 public void pullIntoWithTimeline(
95 final Map<tools.refinery.viatra.runtime.matchers.tuple.Tuple, Timeline<Timestamp>> collector,
96 final boolean flush) {
97 // use all zero timestamps because this node cannot be used in recursive groups anyway
98 for (final Tuple<Object> tuple : ((IncSCCAlg<Object>) transitiveClosureAlgorithm).getTcRelation()) {
99 collector.put(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget()), Timestamp.INSERT_AT_ZERO_TIMELINE);
100 }
101 }
102
103 @Override
104 public void update(Direction direction, tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement,
105 Timestamp timestamp) {
106 if (updateElement.getSize() == 2) {
107 Object source = updateElement.get(0);
108 Object target = updateElement.get(1);
109
110 if (direction == Direction.INSERT) {
111 graphDataSource.insertNode(source);
112 graphDataSource.insertNode(target);
113 graphDataSource.insertEdge(source, target);
114 }
115 if (direction == Direction.DELETE) {
116 graphDataSource.deleteEdgeIfExists(source, target);
117
118 if (((IncSCCAlg<Object>) transitiveClosureAlgorithm).isIsolated(source)) {
119 graphDataSource.deleteNode(source);
120 }
121 if (!source.equals(target) && ((IncSCCAlg<Object>) transitiveClosureAlgorithm).isIsolated(target)) {
122 graphDataSource.deleteNode(target);
123 }
124 }
125 }
126 }
127
128 @Override
129 public void clear() {
130 transitiveClosureAlgorithm.dispose();
131 graphDataSource = new Graph<Object>();
132 transitiveClosureAlgorithm = new IncSCCAlg<Object>(graphDataSource);
133 }
134
135 @Override
136 public void tupleInserted(Object source, Object target) {
137 tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target);
138 propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO);
139 }
140
141 @Override
142 public void tupleDeleted(Object source, Object target) {
143 tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target);
144 propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO);
145 }
146
147}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java
new file mode 100644
index 00000000..6c21a966
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Collection;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.util.Direction;
17import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
18import tools.refinery.viatra.runtime.rete.network.ReteContainer;
19import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
20
21/**
22 * Simply propagates everything. Might be used to join or fork.
23 *
24 * @author Gabor Bergmann
25 */
26public class TransparentNode extends SingleInputNode {
27
28 public TransparentNode(final ReteContainer reteContainer) {
29 super(reteContainer);
30 }
31
32 @Override
33 public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) {
34 propagateUpdate(direction, updateElement, timestamp);
35
36 }
37
38 @Override
39 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
40 propagatePullInto(collector, flush);
41 }
42
43 @Override
44 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
45 propagatePullIntoWithTimestamp(collector, flush);
46 }
47
48}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java
new file mode 100644
index 00000000..8a72138c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
14import tools.refinery.viatra.runtime.rete.network.ReteContainer;
15
16/**
17 * Trims the matchings as specified by a mask.
18 *
19 * @author Gabor Bergmann
20 *
21 */
22public class TrimmerNode extends TransformerNode {
23
24 protected TupleMask mask;
25
26 /**
27 * @param reteContainer
28 * @param mask
29 * The mask used to trim substitutions.
30 */
31 public TrimmerNode(ReteContainer reteContainer, TupleMask mask) {
32 super(reteContainer);
33 this.mask = mask;
34 }
35
36 public TrimmerNode(ReteContainer reteContainer) {
37 super(reteContainer);
38 this.mask = null;
39 }
40
41 /**
42 * @return the mask
43 */
44 public TupleMask getMask() {
45 return mask;
46 }
47
48 /**
49 * @param mask
50 * the mask to set
51 */
52 public void setMask(TupleMask mask) {
53 this.mask = mask;
54 }
55
56 @Override
57 protected Tuple transform(Tuple input) {
58 return mask.transform(input);
59 }
60
61}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java
new file mode 100644
index 00000000..5bfde248
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java
@@ -0,0 +1,321 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann, Tamas Szabo 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import java.util.Collection;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.context.IPosetComparator;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.matchers.util.Direction;
21import tools.refinery.viatra.runtime.matchers.util.IMultiset;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
23import tools.refinery.viatra.runtime.rete.index.MemoryIdentityIndexer;
24import tools.refinery.viatra.runtime.rete.index.MemoryNullIndexer;
25import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer;
26import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver;
27import tools.refinery.viatra.runtime.rete.network.RederivableNode;
28import tools.refinery.viatra.runtime.rete.network.ReteContainer;
29import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup;
30import tools.refinery.viatra.runtime.rete.network.communication.Timestamp;
31import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup;
32import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox;
33import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox;
34import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox;
35
36/**
37 * Timeless uniqueness enforcer node implementation.
38 * <p>
39 * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the
40 * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached
41 * during the evaluation.
42 *
43 * @author Gabor Bergmann
44 * @author Tamas Szabo
45 * @noinstantiate This class is not intended to be instantiated by clients.
46 * @noextend This class is not intended to be subclassed by clients.
47 */
48public class UniquenessEnforcerNode extends AbstractUniquenessEnforcerNode
49 implements RederivableNode, PosetAwareReceiver {
50
51 protected IMultiset<Tuple> memory;
52 /**
53 * @since 1.6
54 */
55 protected IMultiset<Tuple> rederivableMemory;
56 /**
57 * @since 1.6
58 */
59 protected boolean deleteRederiveEvaluation;
60
61 /**
62 * @since 1.7
63 */
64 protected CommunicationGroup currentGroup;
65
66 public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) {
67 this(reteContainer, tupleWidth, false);
68 }
69
70 /**
71 * OPTIONAL ELEMENT - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE
72 *
73 * @since 1.6
74 */
75 protected final TupleMask coreMask;
76 /**
77 * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE
78 *
79 * @since 1.6
80 */
81 protected final TupleMask posetMask;
82 /**
83 * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE
84 *
85 * @since 1.6
86 */
87 protected final IPosetComparator posetComparator;
88
89 /**
90 * @since 1.6
91 */
92 public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth,
93 final boolean deleteRederiveEvaluation) {
94 this(reteContainer, tupleWidth, deleteRederiveEvaluation, null, null, null);
95 }
96
97 /**
98 * @since 1.6
99 */
100 public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth,
101 final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask,
102 final IPosetComparator posetComparator) {
103 super(reteContainer, tupleWidth);
104 this.memory = CollectionsFactory.createMultiset();
105 this.rederivableMemory = CollectionsFactory.createMultiset();
106 reteContainer.registerClearable(this.memory);
107 reteContainer.registerClearable(this.rederivableMemory);
108 this.deleteRederiveEvaluation = deleteRederiveEvaluation;
109 this.coreMask = coreMask;
110 this.posetMask = posetMask;
111 this.posetComparator = posetComparator;
112 this.mailbox = instantiateMailbox();
113 reteContainer.registerClearable(this.mailbox);
114 }
115
116 @Override
117 public void pullInto(final Collection<Tuple> collector, final boolean flush) {
118 for (final Tuple tuple : this.memory.distinctValues()) {
119 collector.add(tuple);
120 }
121 }
122
123 /**
124 * @since 2.8
125 */
126 @Override
127 public Set<Tuple> getTuples() {
128 return this.memory.distinctValues();
129 }
130
131 @Override
132 public boolean isInDRedMode() {
133 return this.deleteRederiveEvaluation;
134 }
135
136 @Override
137 public TupleMask getCoreMask() {
138 return coreMask;
139 }
140
141 @Override
142 public TupleMask getPosetMask() {
143 return posetMask;
144 }
145
146 @Override
147 public IPosetComparator getPosetComparator() {
148 return posetComparator;
149 }
150
151 @Override
152 public void pullIntoWithTimeline(final Map<Tuple, Timeline<Timestamp>> collector, final boolean flush) {
153 throw new UnsupportedOperationException("Use the timely version of this node!");
154 }
155
156 /**
157 * @since 2.0
158 */
159 protected Mailbox instantiateMailbox() {
160 if (coreMask != null && posetMask != null && posetComparator != null) {
161 return new PosetAwareMailbox(this, this.reteContainer);
162 } else {
163 return new BehaviorChangingMailbox(this, this.reteContainer);
164 }
165 }
166
167 @Override
168 public void update(final Direction direction, final Tuple update, final Timestamp timestamp) {
169 updateWithPosetInfo(direction, update, false);
170 }
171
172 @Override
173 public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) {
174 if (this.deleteRederiveEvaluation) {
175 if (updateWithDeleteAndRederive(direction, update, monotone)) {
176 propagate(direction, update, Timestamp.ZERO);
177 }
178 } else {
179 if (updateDefault(direction, update)) {
180 propagate(direction, update, Timestamp.ZERO);
181 }
182 }
183 }
184
185 /**
186 * @since 2.4
187 */
188 protected boolean updateWithDeleteAndRederive(final Direction direction, final Tuple update,
189 final boolean monotone) {
190 boolean propagate = false;
191
192 final int memoryCount = memory.getCount(update);
193 final int rederivableCount = rederivableMemory.getCount(update);
194
195 if (direction == Direction.INSERT) {
196 // INSERT
197 if (rederivableCount != 0) {
198 // the tuple is in the re-derivable memory
199 rederivableMemory.addOne(update);
200 if (rederivableMemory.isEmpty()) {
201 // there is nothing left to be re-derived
202 // this can happen if the INSERT cancelled out a DELETE
203 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
204 }
205 } else {
206 // the tuple is in the main memory
207 propagate = memory.addOne(update);
208 }
209 } else {
210 // DELETE
211 if (rederivableCount != 0) {
212 // the tuple is in the re-derivable memory
213 if (memoryCount != 0) {
214 issueError("[INTERNAL ERROR] Inconsistent state for " + update
215 + " because it is present both in the main and re-derivable memory in the UniquenessEnforcerNode "
216 + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null);
217 }
218
219 try {
220 rederivableMemory.removeOne(update);
221 } catch (final IllegalStateException ex) {
222 issueError(
223 "[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in UniquenessEnforcer "
224 + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(),
225 ex);
226 }
227 if (rederivableMemory.isEmpty()) {
228 // there is nothing left to be re-derived
229 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
230 }
231 } else {
232 // the tuple is in the main memory
233 if (monotone) {
234 propagate = memory.removeOne(update);
235 } else {
236 final int count = memoryCount - 1;
237 if (count > 0) {
238 if (rederivableMemory.isEmpty()) {
239 // there is now something to be re-derived
240 ((RecursiveCommunicationGroup) currentGroup).addRederivable(this);
241 }
242 rederivableMemory.addPositive(update, count);
243 }
244 memory.clearAllOf(update);
245 propagate = true;
246 }
247 }
248 }
249
250 return propagate;
251 }
252
253 /**
254 * @since 2.4
255 */
256 protected boolean updateDefault(final Direction direction, final Tuple update) {
257 boolean propagate = false;
258 if (direction == Direction.INSERT) {
259 // INSERT
260 propagate = memory.addOne(update);
261 } else {
262 // DELETE
263 try {
264 propagate = memory.removeOne(update);
265 } catch (final IllegalStateException ex) {
266 propagate = false;
267 issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in "
268 + this.getClass().getName() + " " + this + " for pattern(s) "
269 + getTraceInfoPatternsEnumerated(), ex);
270 }
271 }
272 return propagate;
273 }
274
275 /**
276 * @since 1.6
277 */
278 @Override
279 public void rederiveOne() {
280 final Tuple update = rederivableMemory.iterator().next();
281 final int count = rederivableMemory.getCount(update);
282 rederivableMemory.clearAllOf(update);
283 memory.addPositive(update, count);
284 // if there is no other re-derivable tuple, then unregister the node itself
285 if (this.rederivableMemory.isEmpty()) {
286 ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this);
287 }
288 propagate(Direction.INSERT, update, Timestamp.ZERO);
289 }
290
291 @Override
292 public ProjectionIndexer getNullIndexer() {
293 if (this.memoryNullIndexer == null) {
294 this.memoryNullIndexer = new MemoryNullIndexer(this.reteContainer, this.tupleWidth,
295 this.memory.distinctValues(), this, this, this.specializedListeners);
296 this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer);
297 }
298 return this.memoryNullIndexer;
299 }
300
301 @Override
302 public ProjectionIndexer getIdentityIndexer() {
303 if (this.memoryIdentityIndexer == null) {
304 this.memoryIdentityIndexer = new MemoryIdentityIndexer(this.reteContainer, this.tupleWidth,
305 this.memory.distinctValues(), this, this, this.specializedListeners);
306 this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer);
307 }
308 return this.memoryIdentityIndexer;
309 }
310
311 @Override
312 public CommunicationGroup getCurrentGroup() {
313 return currentGroup;
314 }
315
316 @Override
317 public void setCurrentGroup(final CommunicationGroup currentGroup) {
318 this.currentGroup = currentGroup;
319 }
320
321}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java
new file mode 100644
index 00000000..c641bf6e
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.single;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13import tools.refinery.viatra.runtime.rete.network.ReteContainer;
14
15/**
16 * A filter node that keeps only those tuples that contain a certain value at a certain position.
17 *
18 * @author Bergmann Gabor
19 *
20 */
21public class ValueBinderFilterNode extends FilterNode {
22
23 int bindingIndex;
24 Object bindingValue;
25
26 /**
27 * @param reteContainer
28 * @param bindingIndex
29 * the position in the tuple that should be bound
30 * @param bindingValue
31 * the value to which the tuple has to be bound
32 */
33 public ValueBinderFilterNode(ReteContainer reteContainer, int bindingIndex, Object bindingValue) {
34 super(reteContainer);
35 this.bindingIndex = bindingIndex;
36 this.bindingValue = bindingValue;
37 }
38
39 @Override
40 public boolean check(Tuple ps) {
41 return bindingValue.equals(ps.get(bindingIndex));
42 }
43
44}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java
new file mode 100644
index 00000000..2055dfe8
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
12
13public class ActiveNodeConflictTrace extends RecipeTraceInfo { // TODO implement PatternTraceInfo
14 RecipeTraceInfo inactiveRecipeTrace;
15 public ActiveNodeConflictTrace(ReteNodeRecipe recipe,
16 RecipeTraceInfo parentRecipeTrace,
17 RecipeTraceInfo inactiveRecipeTrace) {
18 super(recipe, parentRecipeTrace);
19 this.inactiveRecipeTrace = inactiveRecipeTrace;
20 }
21 public RecipeTraceInfo getInactiveRecipeTrace() {
22 return inactiveRecipeTrace;
23 }
24} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java
new file mode 100644
index 00000000..b8c793c5
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.matchers.psystem.PBody;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
16
17/**
18 * Indicates that recipe expresses the finished match set of a query.
19 * @author Bergmann Gabor
20 * @noinstantiate This class is not intended to be instantiated by clients.
21 */
22public class CompiledQuery extends RecipeTraceInfo implements
23 PatternTraceInfo {
24
25 private PQuery query;
26 private final Map<PBody, ? extends RecipeTraceInfo> parentRecipeTracesPerBody;
27
28 /**
29 * @since 1.6
30 */
31 public CompiledQuery(ReteNodeRecipe recipe,
32 Map<PBody, ? extends RecipeTraceInfo> parentRecipeTraces,
33 PQuery query) {
34 super(recipe, parentRecipeTraces.values());
35 parentRecipeTracesPerBody = parentRecipeTraces;
36 this.query = query;
37 }
38 public PQuery getQuery() {
39 return query;
40 }
41
42 @Override
43 public String getPatternName() {
44 return query.getFullyQualifiedName();
45 }
46
47 /**
48 * @since 1.6
49 */
50 public Map<PBody, ? extends RecipeTraceInfo> getParentRecipeTracesPerBody() {
51 return parentRecipeTracesPerBody;
52 }
53
54}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java
new file mode 100644
index 00000000..572e943c
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Set;
16import java.util.stream.Collectors;
17
18import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
19import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
20import tools.refinery.viatra.runtime.matchers.util.Preconditions;
21import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
22
23/**
24 * A trace marker associating a Rete recipe with a query SubPlan.
25 *
26 * <p> The Rete node represented by the recipe is equivalent to the SubPlan.
27 * <p> Invariant: each variable has at most one index associated with it in the tuple, i.e. no duplicates.
28 */
29public class CompiledSubPlan extends PlanningTrace {
30
31 public CompiledSubPlan(SubPlan subPlan, List<PVariable> variablesTuple,
32 ReteNodeRecipe recipe,
33 Collection<? extends RecipeTraceInfo> parentRecipeTraces) {
34 super(subPlan, variablesTuple, recipe, parentRecipeTraces);
35
36 // Make sure that each variable occurs only once
37 Set<PVariable> variablesSet = new HashSet<PVariable>(variablesTuple);
38 Preconditions.checkState(variablesSet.size() == variablesTuple.size(),
39 () -> String.format(
40 "Illegal column duplication (%s) while the query plan %s was compiled into a Rete Recipe %s",
41 variablesTuple.stream().map(PVariable::getName).collect(Collectors.joining(",")),
42 subPlan.toShortString(), recipe));
43 }
44
45 public CompiledSubPlan(SubPlan subPlan, List<PVariable> variablesTuple,
46 ReteNodeRecipe recipe,
47 RecipeTraceInfo... parentRecipeTraces) {
48 this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces));
49 }
50
51}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java
new file mode 100644
index 00000000..8da1e314
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java
@@ -0,0 +1,42 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.Arrays;
12import java.util.Collection;
13
14import tools.refinery.viatra.runtime.matchers.psystem.PBody;
15import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
16
17/**
18 * The recipe projects the finished results of a {@link PBody} onto the list of parameters.
19 * @author Bergmann Gabor
20 *
21 */
22public class ParameterProjectionTrace extends RecipeTraceInfo implements PatternTraceInfo {
23
24 public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe,
25 RecipeTraceInfo... parentRecipeTraces) {
26 this(body, recipe, Arrays.asList(parentRecipeTraces));
27 }
28
29 public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe,
30 Collection<? extends RecipeTraceInfo> parentRecipeTraces) {
31 super(recipe, parentRecipeTraces);
32 this.body = body;
33 }
34
35 PBody body;
36
37 @Override
38 public String getPatternName() {
39 return body.getPattern().getFullyQualifiedName();
40 }
41
42}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java
new file mode 100644
index 00000000..fb7ef062
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11/**
12 * One kind of trace marker that merely establishes the pattern for which the node was built.
13 * @author Bergmann Gabor
14 */
15public interface PatternTraceInfo extends TraceInfo {
16 String getPatternName();
17} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java
new file mode 100644
index 00000000..c1cc3a69
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
18import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
19import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
20
21/**
22 * A trace marker associating a Rete recipe with a query SubPlan.
23 *
24 * <p> The recipe may be an auxiliary node;
25 * see {@link CompiledSubPlan} if it represents the entire SubPlan instead.
26 */
27public class PlanningTrace extends RecipeTraceInfo implements PatternTraceInfo {
28
29 protected SubPlan subPlan;
30 protected List<PVariable> variablesTuple;
31 protected Map<PVariable, Integer> posMapping;
32
33 public PlanningTrace(SubPlan subPlan, List<PVariable> variablesTuple,
34 ReteNodeRecipe recipe,
35 Collection<? extends RecipeTraceInfo> parentRecipeTraces) {
36 super(recipe, parentRecipeTraces);
37 this.subPlan = subPlan;
38 this.variablesTuple = variablesTuple;
39
40 this.posMapping = new HashMap<PVariable, Integer>();
41 for (int i = 0; i < variablesTuple.size(); ++i)
42 posMapping.put(variablesTuple.get(i), i);
43 }
44
45 public PlanningTrace(SubPlan subPlan, List<PVariable> variablesTuple,
46 ReteNodeRecipe recipe,
47 RecipeTraceInfo... parentRecipeTraces) {
48 this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces));
49 }
50
51 public SubPlan getSubPlan() {
52 return subPlan;
53 }
54
55 @Override
56 public String getPatternName() {
57 return subPlan.getBody().getPattern().getFullyQualifiedName();
58 }
59
60 public List<PVariable> getVariablesTuple() {
61 return variablesTuple;
62 }
63
64 public Map<PVariable, Integer> getPosMapping() {
65 return posMapping;
66 }
67
68 /**
69 * Returns a new clone that reinterprets the same compiled form
70 * as the compiled form of a (potentially different) subPlan.
71 * Useful e.g. if child plan turns out to be a no-op, or when promoting a {@link PlanningTrace} to {@link CompiledSubPlan}.
72 */
73 public CompiledSubPlan cloneFor(SubPlan newSubPlan) {
74 return new CompiledSubPlan(newSubPlan,
75 getVariablesTuple(),
76 getRecipe(),
77 getParentRecipeTracesForCloning());
78 }
79
80} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java
new file mode 100644
index 00000000..8f610550
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.List;
16
17import tools.refinery.viatra.runtime.rete.network.Node;
18import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
19
20/**
21 * A trace marker that indicates the recipe for which the node was built.
22 * @author Bergmann Gabor
23 */
24public class RecipeTraceInfo implements TraceInfo {
25 public ReteNodeRecipe getRecipe() {return recipe;}
26 /**
27 * For cloning in case of recursion cut-off points, use {@link #getParentRecipeTracesForCloning()} instead.
28 * @return an unmodifiable view on parent traces, to be constructed before this node (or alongside, in case of recursion)
29 */
30 public List<RecipeTraceInfo> getParentRecipeTraces() {return Collections.unmodifiableList(new ArrayList<>(parentRecipeTraces));}
31 /**
32 * Directly return the underlying collection so that changes to it will be transparent. Use only for recursion-tolerant cloning.
33 * @noreference This method is not intended to be referenced by clients.
34 */
35 public Collection<? extends RecipeTraceInfo> getParentRecipeTracesForCloning() {return parentRecipeTraces;}
36 @Override
37 public Node getNode() {return node;}
38
39 private Node node;
40 ReteNodeRecipe recipe;
41 ReteNodeRecipe shadowedRecipe;
42 Collection<? extends RecipeTraceInfo> parentRecipeTraces;
43
44
45 public RecipeTraceInfo(ReteNodeRecipe recipe, Collection<? extends RecipeTraceInfo> parentRecipeTraces) {
46 super();
47 this.recipe = recipe;
48 this.parentRecipeTraces = parentRecipeTraces; //ParentTraceList.from(parentRecipeTraces);
49 }
50 public RecipeTraceInfo(ReteNodeRecipe recipe, RecipeTraceInfo... parentRecipeTraces) {
51 this(recipe, Arrays.asList(parentRecipeTraces));
52 }
53
54 @Override
55 public boolean propagateToIndexerParent() {return false;}
56 @Override
57 public boolean propagateFromIndexerToSupplierParent() {return false;}
58 @Override
59 public boolean propagateFromStandardNodeToSupplierParent() {return false;}
60 @Override
61 public boolean propagateToProductionNodeParentAlso() {return false;}
62 @Override
63 public void assignNode(Node node) {this.node = node;}
64
65 /**
66 * @param knownRecipe a known recipe that is equivalent to the current recipe
67 */
68 public void shadowWithEquivalentRecipe(ReteNodeRecipe knownRecipe) {
69 this.shadowedRecipe = this.recipe;
70 this.recipe = knownRecipe;
71 }
72
73 /**
74 * Get original recipe shadowed by an equivalent
75 */
76 public ReteNodeRecipe getShadowedRecipe() {
77 return shadowedRecipe;
78 }
79
80
81} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java
new file mode 100644
index 00000000..e1d440db
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import tools.refinery.viatra.runtime.rete.network.Node;
12
13
14/**
15 * Traces the node back to a purpose for which the node was built,
16 * to explain why the node is there and what it means.
17 * @author Bergmann Gabor
18 */
19public interface TraceInfo {
20 boolean propagateToIndexerParent();
21 boolean propagateFromIndexerToSupplierParent();
22 boolean propagateFromStandardNodeToSupplierParent();
23 boolean propagateToProductionNodeParentAlso();
24
25 void assignNode(Node node);
26 Node getNode();
27}
28// /**
29// * The semantics of the tuples contained in this node.
30// * @return a tuple of correct size representing the semantics of each position.
31// * @post not null
32// */
33// Tuple getSemantics(); \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java
new file mode 100644
index 00000000..11e4db32
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.traceability;
10
11import java.util.Collection;
12
13import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe;
14
15// private class AggregatorReferenceIndexTraceInfo extends RecipeTraceInfo {
16// RecipeTraceInfo aggregatorNodeRecipeTrace;
17// public AggregatorReferenceIndexTraceInfo(ProjectionIndexerRecipe recipe,
18// RecipeTraceInfo parentRecipeTrace,
19// RecipeTraceInfo aggregatorNodeRecipeTrace) {
20// super(recipe, parentRecipeTrace);
21// this.aggregatorNodeRecipeTrace = aggregatorNodeRecipeTrace;
22// }
23// public RecipeTraceInfo getAggregatorNodeRecipeTrace() {
24// return aggregatorNodeRecipeTrace;
25// }
26// }
27public class UserRequestTrace extends RecipeTraceInfo {
28 public UserRequestTrace(ReteNodeRecipe recipe,
29 Collection<RecipeTraceInfo> parentRecipeTraces) {
30 super(recipe, parentRecipeTraces);
31 }
32 public UserRequestTrace(ReteNodeRecipe recipe,
33 RecipeTraceInfo... parentRecipeTraces) {
34 super(recipe, parentRecipeTraces);
35 }
36} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java
new file mode 100644
index 00000000..0efc50af
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java
@@ -0,0 +1,58 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.util;
10
11import java.util.Comparator;
12import java.util.Iterator;
13
14/**
15 * A comparator that compares two iterables based on the lexicographic sorting induced by a comparator on elements.
16 * @author Bergmann Gabor
17 *
18 */
19public class LexicographicComparator<T> implements Comparator<Iterable<? extends T>> {
20
21 final Comparator<T> elementComparator;
22
23 public LexicographicComparator(Comparator<T> elementComparator) {
24 super();
25 this.elementComparator = elementComparator;
26 }
27
28 @Override
29 public int compare(Iterable<? extends T> o1, Iterable<? extends T> o2) {
30 Iterator<? extends T> it1 = o1.iterator();
31 Iterator<? extends T> it2 = o2.iterator();
32
33 boolean has1, has2, bothHaveNext;
34 do {
35 has1 = it1.hasNext();
36 has2 = it2.hasNext();
37 bothHaveNext = has1 && has2;
38 if (bothHaveNext) {
39 T element1 = it1.next();
40 T element2 = it2.next();
41 int elementComparison = elementComparator.compare(element1, element2);
42 if (elementComparison != 0)
43 return elementComparison;
44 }
45 } while (bothHaveNext);
46 if (has1 && !has2) {
47 return +1;
48 } else if (!has1 && has2) {
49 return -1;
50 } else /*if (!has1 && !has2)*/ {
51 return 0;
52 }
53 }
54
55
56
57
58}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java
new file mode 100644
index 00000000..96cc445f
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java
@@ -0,0 +1,111 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.util;
11
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
13import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
14import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy;
15import tools.refinery.viatra.runtime.rete.construction.basiclinear.BasicLinearLayout;
16import tools.refinery.viatra.runtime.rete.construction.quasitree.QuasiTreeLayout;
17import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationGroup;
18
19/**
20 * Feature switches.
21 * @author Gabor Bergmann
22 * @noreference
23 */
24public class Options {
25
26 public enum NodeSharingOption {
27 NEVER, // not recommended, patternmatcher leaks possible
28 INDEXER_AND_REMOTEPROXY, ALL
29 }
30
31 public static final NodeSharingOption nodeSharingOption = NodeSharingOption.ALL;
32 public static final boolean releaseOnetimeIndexers = true; // effective only
33 // with
34 // nodesharing
35 // ==NEVER
36
37 public enum InjectivityStrategy {
38 EAGER, LAZY
39 }
40
41 public static final InjectivityStrategy injectivityStrategy = InjectivityStrategy.EAGER;
42
43 public static final boolean enableInheritance = true;
44
45 // public final static boolean useComplementerMask = true;
46
47 public static final boolean employTrivialIndexers = true;
48
49 // public final static boolean synchronous = false;
50
51 public static final int numberOfLocalContainers = 1;
52 public static final int firstFreeContainer = 0; // 0 if head container is
53 // free to contain pattern
54 // bodies, 1 otherwise
55
56 /**
57 * Enable for internal debugging of Rete communication scheme;
58 * catches cases where the topological sort is violated by a message sent "backwards"
59 * @since 1.6
60 */
61 public static final boolean MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING = false;
62
63 /**
64 * Enable for internal debugging of message delivery in {@link TimelyCommunicationGroup}s;
65 * catches cases when there is a violation of increasing timestamps during message delivery within a group.
66 * @since 2.3
67 */
68 public static final boolean MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS = false;
69
70 /**
71 *
72 * @author Gabor Bergmann
73 * @noreference
74 */
75 public enum BuilderMethod {
76 LEGACY, // ONLY with GTASM
77 PSYSTEM_BASIC_LINEAR, PSYSTEM_QUASITREE;
78 /**
79 * @since 1.5
80 */
81 public IQueryPlannerStrategy layoutStrategy(IQueryBackendContext bContext, IQueryBackendHintProvider hintProvider) {
82 switch (this) {
83 case PSYSTEM_BASIC_LINEAR:
84 return new BasicLinearLayout(bContext);
85 case PSYSTEM_QUASITREE:
86 return new QuasiTreeLayout(bContext, hintProvider);
87 default:
88 throw new UnsupportedOperationException();
89 }
90 }
91 }
92
93 public static final BuilderMethod builderMethod =
94 // BuilderMethod.PSYSTEM_BASIC_LINEAR;
95 BuilderMethod.PSYSTEM_QUASITREE;
96
97 public enum FunctionalDependencyOption {
98 OFF,
99 OPPORTUNISTIC
100 }
101 public static final FunctionalDependencyOption functionalDependencyOption =
102 FunctionalDependencyOption.OPPORTUNISTIC;
103
104 public enum PlanTrimOption {
105 OFF,
106 OPPORTUNISTIC
107 }
108 public static final PlanTrimOption planTrimOption =
109 PlanTrimOption.OPPORTUNISTIC;
110
111}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java
new file mode 100644
index 00000000..8b147cf6
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java
@@ -0,0 +1,92 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.rete.util;
11
12import java.util.Comparator;
13
14/**
15 * Comparing agent for an ordering. Terminology: the "preferred" item will register as LESS.
16 *
17 * @author Gabor Bergmann
18 *
19 */
20public abstract class OrderingCompareAgent<T> {
21 protected T a;
22 protected T b;
23
24 /**
25 * @param a
26 * @param b
27 */
28 public OrderingCompareAgent(T a, T b) {
29 super();
30 this.a = a;
31 this.b = b;
32 }
33
34 int result = 0;
35
36 protected abstract void doCompare();
37
38 /**
39 * @return the result
40 */
41 public int compare() {
42 doCompare();
43 return result;
44 }
45
46 // COMPARISON HELPERS
47 protected boolean isUnknown() {
48 return result == 0;
49 }
50
51 /**
52 * @pre result == 0
53 */
54 protected boolean consider(int partial) {
55 if (isUnknown())
56 result = partial;
57 return isUnknown();
58 }
59
60 protected boolean swallowBoolean(boolean x) {
61 return x;
62 }
63
64 // PREFERENCE FUNCTIONS
65 protected static int dontCare() {
66 return 0;
67 }
68
69 protected static int preferTrue(boolean b1, boolean b2) {
70 return (b1 ^ b2) ? (b1 ? -1 : +1) : 0;
71 }
72
73 protected static int preferFalse(boolean b1, boolean b2) {
74 return (b1 ^ b2) ? (b2 ? -1 : +1) : 0;
75 }
76
77 protected static <U> int preferLess(Comparable<U> c1, U c2) {
78 return c1.compareTo(c2);
79 }
80
81 protected static <U> int preferLess(U c1, U c2, Comparator<U> comp) {
82 return comp.compare(c1, c2);
83 }
84
85 protected static <U> int preferMore(Comparable<U> c1, U c2) {
86 return -c1.compareTo(c2);
87 }
88 protected static <U> int preferMore(U c1, U c2, Comparator<U> comp) {
89 return -comp.compare(c1, c2);
90 }
91
92}
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java
new file mode 100644
index 00000000..6e685253
--- /dev/null
+++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.rete.util;
10
11import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
12import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption;
13import tools.refinery.viatra.runtime.rete.matcher.DRedReteBackendFactory;
14
15/**
16 * Provides key objects (of type {@link QueryHintOption}) for {@link QueryEvaluationHint}s.
17 * @author Gabor Bergmann
18 * @since 1.5
19 */
20public final class ReteHintOptions {
21
22 private ReteHintOptions() {/*Utility class constructor*/}
23
24 public static final QueryHintOption<Boolean> useDiscriminatorDispatchersForConstantFiltering =
25 hintOption("useDiscriminatorDispatchersForConstantFiltering", true);
26
27 public static final QueryHintOption<Boolean> prioritizeConstantFiltering =
28 hintOption("prioritizeConstantFiltering", true);
29
30 public static final QueryHintOption<Boolean> cacheOutputOfEvaluatorsByDefault =
31 hintOption("cacheOutputOfEvaluatorsByDefault", true);
32
33 /**
34 * The incremental query evaluator backend can evaluate recursive patterns.
35 * However, by default, instance models that contain cycles are not supported with recursive queries
36 * and can lead to incorrect query results.
37 * Enabling Delete And Rederive (DRED) mode guarantees that recursive query evaluation leads to correct results in these cases as well.
38 *
39 * <p> As DRED may diminish the performance of incremental maintenance, it is not enabled by default.
40 * @since 1.6
41 * @deprecated Use {@link DRedReteBackendFactory} instead of setting this option to true.
42 */
43 @Deprecated
44 public static final QueryHintOption<Boolean> deleteRederiveEvaluation =
45 hintOption("deleteRederiveEvaluation", false);
46
47 /**
48 * This hint allows the query planner to take advantage of "weakened alternative" suggestions of the meta context.
49 * For instance, enumerable unary type constraints may be substituted with a simple type filtering where sufficient.
50 *
51 * @since 1.6
52 */
53 public static final QueryHintOption<Boolean> expandWeakenedAlternativeConstraints =
54 hintOption("expandWeakenedAlternativeConstraints", true);
55
56 // internal helper for conciseness
57 private static <T> QueryHintOption<T> hintOption(String hintKeyLocalName, T defaultValue) {
58 return new QueryHintOption<>(ReteHintOptions.class, hintKeyLocalName, defaultValue);
59 }
60}
diff --git a/subprojects/viatra-runtime/about.html b/subprojects/viatra-runtime/about.html
new file mode 100644
index 00000000..d1d5593a
--- /dev/null
+++ b/subprojects/viatra-runtime/about.html
@@ -0,0 +1,26 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
2<html>
3<!--
4 Copyright (c) 2017, Eclipse.org Foundation, Inc.
5
6 SPDX-License-Identifier: LicenseRef-EPL-Steward
7-->
8<head>
9<title>About</title>
10<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
11</head>
12<body lang="EN-US">
13<h2>About This Content</h2>
14
15<p>March 18, 2019</p>
16<h3>License</h3>
17
18<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
19Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available at <a href="http://www.eclipse.org/org/documents/epl-v20.php">http://www.eclipse.org/legal/epl-v20.html</a>.
20For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
21
22<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
23apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
24indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
25</body>
26</html>
diff --git a/subprojects/viatra-runtime/build.gradle.kts b/subprojects/viatra-runtime/build.gradle.kts
new file mode 100644
index 00000000..c4bd9129
--- /dev/null
+++ b/subprojects/viatra-runtime/build.gradle.kts
@@ -0,0 +1,15 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 implementation(libs.slf4j.log4j)
13 implementation(libs.eclipseCollections)
14 implementation(libs.eclipseCollections.api)
15}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java
new file mode 100644
index 00000000..a2ae41e3
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/CancellationToken.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime;
7
8@FunctionalInterface
9public interface CancellationToken {
10 CancellationToken NONE = () -> {};
11
12 void checkCancelled();
13}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java
new file mode 100644
index 00000000..32a3430d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java
@@ -0,0 +1,363 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11import tools.refinery.viatra.runtime.api.scope.QueryScope;
12import tools.refinery.viatra.runtime.internal.apiimpl.ViatraQueryEngineImpl;
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.backend.*;
15
16import java.lang.reflect.InvocationTargetException;
17import java.util.concurrent.Callable;
18
19/**
20 * Advanced interface to a VIATRA incremental evaluation engine.
21 *
22 * <p>
23 * You can create a new, private, unmanaged {@link AdvancedViatraQueryEngine} instance using
24 * {@link #createUnmanagedEngine(QueryScope)}. Additionally, you can access the advanced interface on any
25 * {@link ViatraQueryEngine} by {@link AdvancedViatraQueryEngine#from(ViatraQueryEngine)}.
26 *
27 * <p>
28 * While the default interface {@link ViatraQueryEngine}, is suitable for most users, this advanced interface provides more
29 * control over the engine. The most important added functionality is the following:
30 * <ul>
31 * <li>You can have tighter control over the lifecycle of the engine, if you create a private, unmanaged engine
32 * instance. For instance, a (non-managed) engine can be disposed in order to detach from the EMF model and stop
33 * listening on update notifications. The indexes built previously in the engine can then be garbage collected, even if
34 * the model itself is retained. Total lifecycle control is only available for private, unmanaged engines (created using
35 * {@link #createUnmanagedEngine(QueryScope)}); a managed engine (obtained via {@link ViatraQueryEngine#on(QueryScope)}) is
36 * shared among clients and can not be disposed or wiped.
37 * <li>You can add and remove listeners to receive notification when the model or the match sets change.
38 * <li>You can add and remove listeners to receive notification on engine lifecycle events, such as creation of new
39 * matchers. For instance, if you explicitly share a private, unmanaged engine between multiple sites, you should
40 * register a callback using {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client
41 * has called the destructive methods {@link #dispose()} or {@link #wipe()}.
42 * </ul>
43 *
44 * @author Bergmann Gabor
45 * @noextend This class is not intended to be subclassed by clients.
46 */
47public abstract class AdvancedViatraQueryEngine extends ViatraQueryEngine {
48
49 /**
50 * Creates a new unmanaged VIATRA Query engine to evaluate queries over a given scope specified by an {@link QueryScope}.
51 *
52 * <p> Repeated invocations will return different instances, so other clients are unable to independently access
53 * and influence the returned engine. Note that unmanaged engines do not benefit from some performance improvements
54 * that stem from sharing incrementally maintained indices and caches between multiple clients using the same managed
55 * engine instance.
56 *
57 * <p>
58 * Client is responsible for the lifecycle of the returned engine, hence the usage of the advanced interface
59 * {@link AdvancedViatraQueryEngine}.
60 *
61 * <p>
62 * The match set of any patterns will be incrementally refreshed upon updates from this scope.
63 *
64 * @param scope
65 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
66 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
67 * @return the advanced interface to a newly created unmanaged engine
68 * @since 0.9
69 */
70 public static AdvancedViatraQueryEngine createUnmanagedEngine(QueryScope scope) {
71 return new ViatraQueryEngineImpl(null, scope);
72 }
73
74 /**
75 * Creates a new unmanaged VIATRA Query engine to evaluate queries over a given scope specified by an {@link QueryScope}.
76 *
77 * <p> Repeated invocations will return different instances, so other clients are unable to independently access
78 * and influence the returned engine. Note that unmanaged engines do not benefit from some performance improvements
79 * that stem from sharing incrementally maintained indices and caches between multiple clients using the same managed
80 * engine instance.
81 *
82 * <p>
83 * Client is responsible for the lifecycle of the returned engine, hence the usage of the advanced interface
84 * {@link AdvancedViatraQueryEngine}.
85 *
86 * <p>
87 * The match set of any patterns will be incrementally refreshed upon updates from this scope.
88 *
89 * @param scope
90 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
91 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
92 * @return the advanced interface to a newly created unmanaged engine
93 * @since 1.4
94 */
95 public static AdvancedViatraQueryEngine createUnmanagedEngine(QueryScope scope, ViatraQueryEngineOptions options) {
96 return new ViatraQueryEngineImpl(null, scope, options);
97 }
98
99 /**
100 * Provides access to a given existing engine through the advanced interface.
101 *
102 * <p>
103 * Caveat: if the referenced engine is managed (i.e. created via {@link ViatraQueryEngine#on(QueryScope)}), the advanced
104 * methods {@link #dispose()} and {@link #wipe()} will not be allowed.
105 *
106 * @param engine
107 * the engine to access using the advanced interface
108 * @return a reference to the same engine conforming to the advanced interface
109 */
110 public static AdvancedViatraQueryEngine from(ViatraQueryEngine engine) {
111 return (AdvancedViatraQueryEngine) engine;
112 }
113
114 /**
115 * Add an engine lifecycle listener to this engine instance.
116 *
117 * @param listener
118 * the {@link ViatraQueryEngineLifecycleListener} that should listen to lifecycle events from this engine
119 */
120 public abstract void addLifecycleListener(ViatraQueryEngineLifecycleListener listener);
121
122 /**
123 * Remove an existing lifecycle listener from this engine instance.
124 *
125 * @param listener
126 * the {@link ViatraQueryEngineLifecycleListener} that should not listen to lifecycle events from this
127 * engine anymore
128 */
129 public abstract void removeLifecycleListener(ViatraQueryEngineLifecycleListener listener);
130
131 /**
132 * Add an model update event listener to this engine instance (that fires its callbacks according to its
133 * notification level).
134 *
135 * @param listener
136 * the {@link ViatraQueryModelUpdateListener} that should listen to model update events from this engine.
137 */
138 public abstract void addModelUpdateListener(ViatraQueryModelUpdateListener listener);
139
140 /**
141 * Remove an existing model update event listener to this engine instance.
142 *
143 * @param listener
144 * the {@link ViatraQueryModelUpdateListener} that should not listen to model update events from this engine
145 * anymore
146 */
147 public abstract void removeModelUpdateListener(ViatraQueryModelUpdateListener listener);
148
149 /**
150 * Registers low-level callbacks for match appearance and disappearance on this pattern matcher.
151 *
152 * <p>
153 * <b>Caution: </b> This is a low-level callback that is invoked when the pattern matcher is not necessarily in a
154 * consistent state yet. Importantly, no model modification permitted during the callback. Most users should use the
155 * databinding support ({@link org.eclipse.viatra.addon.databinding.runtime.api.ViatraObservables ViatraObservables}) or the event-driven API
156 * ({@link org.eclipse.viatra.transformation.evm.api.EventDrivenVM EventDrivenVM}) instead.
157 *
158 * <p>
159 * Performance note: expected to be much more efficient than polling at {@link #addCallbackAfterUpdates(Runnable)},
160 * but prone to "signal hazards", e.g. spurious match appearances that will disappear immediately afterwards.
161 *
162 * <p>
163 * The callback can be unregistered via {@link #removeCallbackOnMatchUpdate(IMatchUpdateListener)}.
164 *
165 * @param fireNow
166 * if true, appearCallback will be immediately invoked on all current matches as a one-time effect. See
167 * also {@link ViatraQueryMatcher#forEachMatch(IMatchProcessor)}.
168 * @param listener
169 * the listener that will be notified of each new match that appears or disappears, starting from now.
170 * @param matcher
171 * the {@link ViatraQueryMatcher} for which this listener should be active
172 */
173 public abstract <Match extends IPatternMatch> void addMatchUpdateListener(ViatraQueryMatcher<Match> matcher,
174 IMatchUpdateListener<? super Match> listener, boolean fireNow);
175
176 /**
177 * Remove an existing match update event listener to this engine instance.
178 *
179 * @param matcher
180 * the {@link ViatraQueryMatcher} for which this listener should not be active anymore
181 * @param listener
182 * the {@link IMatchUpdateListener} that should not receive the callbacks anymore
183 */
184 public abstract <Match extends IPatternMatch> void removeMatchUpdateListener(ViatraQueryMatcher<Match> matcher,
185 IMatchUpdateListener<? super Match> listener);
186
187
188 /**
189 * Access a pattern matcher based on a {@link IQuerySpecification}, overriding some of the default query evaluation hints.
190 * Multiple calls may return the same matcher depending on the actual evaluation hints.
191 *
192 * <p> It is guaranteed that this method will always return a matcher instance which is functionally compatible
193 * with the requested functionality (see {@link IMatcherCapability}).
194 * Otherwise, the query evaluator is free to ignore any hints.
195 *
196 * <p> For stateful query backends (Rete), hints may be effective only the first time a matcher is created.
197 * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query
198 * @return a pattern matcher corresponding to the specification
199 * @param optionalEvaluationHints additional / overriding options on query evaluation; passing null means default options associated with the query
200 * @throws ViatraQueryRuntimeException if the matcher could not be initialized
201 * @since 0.9
202 */
203 public abstract <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getMatcher(
204 IQuerySpecification<Matcher> querySpecification,
205 QueryEvaluationHint optionalEvaluationHints);
206
207 /**
208 * Initializes matchers for a group of patterns as one step (optionally overriding some of the default query evaluation hints).
209 * If some of the pattern matchers are already
210 * constructed in the engine, no task is performed for them.
211 *
212 * <p>
213 * This preparation step has the advantage that it prepares pattern matchers for an arbitrary number of patterns in a
214 * single-pass traversal of the model.
215 * This is typically more efficient than traversing the model each time an individual pattern matcher is initialized on demand.
216 * The performance benefit only manifests itself if the engine is not in wildcard mode.
217 *
218 * @param queryGroup a {@link IQueryGroup} identifying a set of VIATRA queries
219 * @param optionalEvaluationHints additional / overriding options on query evaluation; passing null means default options associated with each query
220 * @throws ViatraQueryRuntimeException
221 * if there was an error in preparing the engine
222 * @since 0.9
223 */
224 public abstract void prepareGroup(IQueryGroup queryGroup, QueryEvaluationHint optionalEvaluationHints);
225
226 /**
227 * Indicates whether the engine is managed, i.e. the default engine assigned to the given scope root by
228 * {@link ViatraQueryEngine#on(QueryScope)}.
229 *
230 * <p>
231 * If the engine is managed, there may be other clients using it, as all calls to
232 * {@link ViatraQueryEngine#on(QueryScope)} return the same managed engine instance for a given scope root. Therefore the
233 * destructive methods {@link #wipe()} and {@link #dispose()} are not allowed.
234 *
235 * <p>
236 * On the other hand, if the engine is unmanaged (i.e. a private instance created using
237 * {@link #createUnmanagedEngine(QueryScope)}), then {@link #wipe()} and {@link #dispose()} can be called. If you
238 * explicitly share a private, unmanaged engine between multiple sites, register a callback using
239 * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called these
240 * destructive methods.
241 *
242 * @return true if the engine is managed, and therefore potentially shared with other clients querying the same EMF
243 * model
244 */
245 public abstract boolean isManaged();
246
247 /**
248 * Indicates whether the engine is in a tainted, inconsistent state due to some internal errors. If true, results
249 * are no longer reliable; engine should be disposed.
250 *
251 * <p>
252 * The engine is in a tainted state if any of its internal processes report back a fatal error. The
253 * {@link ViatraQueryEngineLifecycleListener} interface provides a callback method for entering the tainted state.
254 *
255 * @return the tainted state
256 */
257 public abstract boolean isTainted();
258
259 /**
260 * Discards any pattern matcher caches and forgets known patterns. The base index built directly on the underlying
261 * EMF model, however, is kept in memory to allow reuse when new pattern matchers are built. Use this method if you
262 * have e.g. new versions of the same patterns, to be matched on the same model.
263 *
264 * <p>
265 * Matcher objects will continue to return stale results. If no references are retained to the matchers, they can
266 * eventually be GC'ed.
267 * <p>
268 * Disallowed if the engine is managed (see {@link #isManaged()}), as there may be other clients using it.
269 * <p>
270 * If you explicitly share a private, unmanaged engine between multiple sites, register a callback using
271 * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called this
272 * destructive method.
273 *
274 * @throws UnsupportedOperationException
275 * if engine is managed
276 */
277 public abstract void wipe();
278
279 /**
280 * Completely disconnects and dismantles the engine. Cannot be reversed.
281 * <p>
282 * Matcher objects will continue to return stale results. If no references are retained to the matchers or the
283 * engine, they can eventually be GC'ed, and they won't block the EMF model from being GC'ed anymore.
284 * <p>
285 * The base indexer (see {@link #getBaseIndex()}) built on the model will be disposed alongside the engine, unless
286 * the user has manually added listeners on the base index that were not removed yet.
287 * <p>
288 * Disallowed if the engine is managed (see {@link #isManaged()}), as there may be other clients using it.
289 * <p>
290 * If you explicitly share a private, unmanaged engine between multiple sites, register a callback using
291 * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called this
292 * destructive method.
293 *
294 * @throws UnsupportedOperationException
295 * if engine is managed
296 */
297 public abstract void dispose();
298
299 /**
300 * Provides access to the selected query backend component of the VIATRA Query engine.
301 * @noreference for internal use only
302 * @throws ViatraQueryRuntimeException
303 */
304 public abstract IQueryBackend getQueryBackend(IQueryBackendFactory iQueryBackendFactory);
305
306 /**
307 * Access an existing pattern matcher based on a {@link IQuerySpecification}, and optional hints override.
308 * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification
309 * @param optionalOverrideHints a {@link QueryEvaluationHint} that may override the pattern hints (can be null)
310 * @return a pattern matcher corresponding to the specification, <code>null</code> if a matcher does not exist yet.
311 * @since 1.4
312 */
313 public abstract <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getExistingMatcher(IQuerySpecification<Matcher> querySpecification, QueryEvaluationHint optionalOverrideHints);
314
315 /**
316 * Returns the immutable {@link ViatraQueryEngineOptions} of the engine.
317 *
318 * @return the engine options
319 * @since 1.4
320 */
321 public abstract ViatraQueryEngineOptions getEngineOptions();
322
323 /**
324 * Return the underlying result provider for the given matcher.
325 *
326 * @beta This method may change in future versions
327 * @since 1.4
328 * @noreference This method is considered internal API
329 */
330 public abstract IQueryResultProvider getResultProviderOfMatcher(ViatraQueryMatcher<? extends IPatternMatch> matcher);
331
332 /**
333 * The given callable will be executed, and all update propagation in stateful query backends
334 * will be delayed until the execution is done. Within the callback, these backends will provide stale results.
335 *
336 * <p> It is optional for a {@link IQueryBackend} to support the delaying of update propagation; stateless backends will display up-to-date results.
337 * In this case, the given callable shall be executed, and the update propagation shall happen just like in non-delayed execution.
338 *
339 * <p> Example: in the Rete network, no messages will be propagated until the given callable is executed.
340 * After the execution of the callable, all accumulated messages will be delivered.
341 *
342 * <p> The purpose of this method is that stateful query backends may save work when multiple model modifications are performed within the callback that partially cancel each other out.
343 *
344 * @param callable the callable to be executed
345 * @return the result of the callable
346 * @since 1.6
347 */
348 public abstract <V> V delayUpdatePropagation(Callable<V> callable) throws InvocationTargetException;
349
350 /**
351 * Returns true if the update propagation in this engine is currently delayed, false otherwise.
352 *
353 * @see {@link #delayUpdatePropagation(Callable)}
354 * @since 1.6
355 */
356 public abstract boolean isUpdatePropagationDelayed();
357
358 /**
359 * Returns true if the {@link #dispose()} method was called on this engine previously.
360 * @since 2.0
361 */
362 public abstract boolean isDisposed();
363}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java
new file mode 100644
index 00000000..b4de2b70
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java
@@ -0,0 +1,166 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import java.util.Arrays;
13
14import tools.refinery.viatra.runtime.api.impl.BasePatternMatch;
15
16/**
17 * Generic signature object implementation.
18 *
19 * See also the generated matcher and signature of the pattern, with pattern-specific API simplifications.
20 *
21 * @author Bergmann Gábor
22 * @since 0.9
23 *
24 */
25public abstract class GenericPatternMatch extends BasePatternMatch {
26
27 private final GenericQuerySpecification<? extends GenericPatternMatcher> specification;
28 private final Object[] array;
29
30 private GenericPatternMatch(GenericQuerySpecification<? extends GenericPatternMatcher> specification, Object[] array) {
31 super();
32 this.specification = specification;
33 this.array = array;
34 }
35
36 @Override
37 public Object get(String parameterName) {
38 Integer index = specification.getPositionOfParameter(parameterName);
39 return index == null ? null : array[index];
40 }
41
42 @Override
43 public Object get(int position) {
44 return array[position];
45 }
46
47 @Override
48 public boolean set(String parameterName, Object newValue) {
49 if (!isMutable()) throw new UnsupportedOperationException();
50 Integer index = specification.getPositionOfParameter(parameterName);
51 if (index == null)
52 return false;
53 array[index] = newValue;
54 return true;
55 }
56
57 @Override
58 public Object[] toArray() {
59 return Arrays.copyOf(array, array.length);
60 }
61
62 @Override
63 public int hashCode() {
64 final int prime = 31;
65 int result = 1;
66 for (int i = 0; i < array.length; ++i)
67 result = prime * result + ((array[i] == null) ? 0 : array[i].hashCode());
68 return result;
69 }
70
71 @Override
72 public boolean equals(Object obj) {
73 if (this == obj)
74 return true;
75 if (!(obj instanceof GenericPatternMatch)) { // this should be infrequent
76 if (obj == null)
77 return false;
78 if (!(obj instanceof IPatternMatch))
79 return false;
80 IPatternMatch other = (IPatternMatch) obj;
81 if (!specification().equals(other.specification()))
82 return false;
83 return Arrays.deepEquals(array, other.toArray());
84 }
85 final GenericPatternMatch other = (GenericPatternMatch) obj;
86 return specification().equals(other.specification()) && Arrays.deepEquals(array, other.array);
87 }
88
89 @Override
90 public String prettyPrint() {
91 StringBuilder result = new StringBuilder();
92 for (int i = 0; i < array.length; ++i) {
93 if (i != 0)
94 result.append(", ");
95 result.append("\"" + parameterNames().get(i) + "\"=" + prettyPrintValue(array[i]));
96 }
97 return result.toString();
98 }
99
100 @Override
101 public GenericQuerySpecification<? extends GenericPatternMatcher> specification() {
102 return specification;
103 }
104
105 /**
106 * Returns an empty, mutable match.
107 * Fields of the mutable match can be filled to create a partial match, usable as matcher input.
108 *
109 * @return the empty match
110 */
111 public static GenericPatternMatch newEmptyMatch(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
112 return new Mutable(specification, new Object[specification.getParameters().size()]);
113 }
114
115 /**
116 * Returns a mutable (partial) match.
117 * Fields of the mutable match can be filled to create a partial match, usable as matcher input.
118 *
119 * @param parameters
120 * the fixed value of pattern parameters, or null if not bound.
121 * @return the new, mutable (partial) match object.
122 */
123 public static GenericPatternMatch newMutableMatch(GenericQuerySpecification<? extends GenericPatternMatcher> specification, Object... parameters) {
124 return new Mutable(specification, parameters);
125 }
126
127 /**
128 * Returns a new (partial) match.
129 * This can be used e.g. to call the matcher with a partial match.
130 *
131 * <p>The returned match will be immutable. Use {@link #newEmptyMatch(GenericQuerySpecification)} to obtain a mutable match object.
132 *
133 * @param parameters
134 * the fixed value of pattern parameters, or null if not bound.
135 * @return the (partial) match object.
136 */
137 public static GenericPatternMatch newMatch(GenericQuerySpecification<? extends GenericPatternMatcher> specification, Object... parameters) {
138 return new Immutable(specification, Arrays.copyOf(parameters, parameters.length));
139 }
140
141 @Override
142 public IPatternMatch toImmutable() {
143 return isMutable() ? newMatch(specification, array) : this;
144 }
145
146 static final class Mutable extends GenericPatternMatch {
147 Mutable(GenericQuerySpecification<? extends GenericPatternMatcher> specification, Object[] array) {
148 super(specification, array);
149 }
150
151 @Override
152 public boolean isMutable() {
153 return true;
154 }
155 }
156 static final class Immutable extends GenericPatternMatch {
157 Immutable(GenericQuerySpecification<? extends GenericPatternMatcher> specification, Object[] array) {
158 super(specification, array);
159 }
160
161 @Override
162 public boolean isMutable() {
163 return false;
164 }
165 }
166}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java
new file mode 100644
index 00000000..9a3fbb44
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import tools.refinery.viatra.runtime.api.impl.BaseMatcher;
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14
15/**
16 * This is a generic pattern matcher for any VIATRA pattern, with "interpretative" query execution.
17 * To use the pattern matcher on a given model, obtain a {@link GenericQuerySpecification} first, then
18 * invoke e.g. {@link GenericQuerySpecification#getMatcher(ViatraQueryEngine)}.
19 * in conjunction with {@link ViatraQueryEngine#on(tools.refinery.viatra.runtime.api.scope.QueryScope)}.
20 * <p>
21 * Whenever available, consider using the pattern-specific generated matcher API instead.
22 *
23 * <p>
24 * Matches of the pattern will be represented as {@link GenericPatternMatch}.
25 *
26 * @author Bergmann Gábor
27 * @see GenericPatternMatch
28 * @see GenericMatchProcessor
29 * @see GenericQuerySpecification
30 * @since 0.9
31 */
32public class GenericPatternMatcher extends BaseMatcher<GenericPatternMatch> {
33
34 /**
35 * @since 1.4
36 */
37 public GenericPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
38 super(specification);
39 }
40
41 @Override
42 public GenericPatternMatch arrayToMatch(Object[] parameters) {
43 return GenericPatternMatch.newMatch(getSpecification(), parameters);
44 }
45
46 @Override
47 public GenericPatternMatch arrayToMatchMutable(Object[] parameters) {
48 return GenericPatternMatch.newMutableMatch(getSpecification(), parameters);
49 }
50
51 @Override
52 protected GenericPatternMatch tupleToMatch(Tuple t) {
53 return new GenericPatternMatch.Immutable(getSpecification(), /*avoid re-cloning*/t.getElements());
54 }
55
56 @SuppressWarnings("unchecked")
57 @Override
58 public GenericQuerySpecification<? extends GenericPatternMatcher> getSpecification() {
59 return (GenericQuerySpecification<? extends GenericPatternMatcher>)querySpecification;
60 }
61
62 /**
63 * Internal method for {@link GenericQuerySpecification}
64 * @noreference
65 */
66 static <Matcher extends GenericPatternMatcher> GenericPatternMatcher instantiate(GenericQuerySpecification<Matcher> querySpecification) {
67 return new GenericPatternMatcher(querySpecification);
68 }
69
70 /**
71 * Internal method for {@link GenericQuerySpecification}
72 * @noreference
73 */
74 static <Matcher extends GenericPatternMatcher> GenericPatternMatcher instantiate(ViatraQueryEngine engine, GenericQuerySpecification<Matcher> querySpecification) {
75 // check if matcher already exists
76 GenericPatternMatcher matcher = engine.getExistingMatcher(querySpecification);
77 if (matcher == null) {
78 matcher = engine.getMatcher(querySpecification);
79 }
80 return matcher;
81 }
82
83}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java
new file mode 100644
index 00000000..a5661bc9
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Mark Czotter, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11import java.util.Arrays;
12import java.util.HashSet;
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17import tools.refinery.viatra.runtime.api.impl.BaseQueryGroup;
18
19/**
20 * Generic implementation of {@link IQueryGroup}, covering an arbitrarily chosen set of patterns. Use the public
21 * constructor or static GenericQueryGroup.of(...) methods to instantiate.
22 *
23 * @author Mark Czotter
24 *
25 */
26public class GenericQueryGroup extends BaseQueryGroup {
27
28 private final Set<IQuerySpecification<?>> patterns;
29
30 /**
31 * Creates a GenericQueryGroup object with a set of patterns.
32 *
33 * @param patterns
34 */
35 public GenericQueryGroup(Set<IQuerySpecification<?>> patterns) {
36 this.patterns = patterns;
37 }
38
39 @Override
40 public Set<IQuerySpecification<?>> getSpecifications() {
41 return patterns;
42 }
43
44 /**
45 * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects.
46 *
47 * @since 2.0
48 */
49 public static IQueryGroup of(Stream<IQuerySpecification<?>> querySpecifications) {
50 return new GenericQueryGroup(querySpecifications.collect(Collectors.toSet()));
51 }
52
53 /**
54 * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects.
55 *
56 * @param querySpecifications
57 */
58 public static IQueryGroup of(Set<IQuerySpecification<?>> querySpecifications) {
59 return new GenericQueryGroup(querySpecifications);
60 }
61
62 /**
63 * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects.
64 *
65 * @param querySpecifications
66 */
67 public static IQueryGroup of(IQuerySpecification<?>... querySpecifications) {
68 return of(new HashSet<IQuerySpecification<?>>(Arrays.asList(querySpecifications)));
69 }
70
71 /**
72 * Creates a generic {@link IQueryGroup} instance from other {@link IQueryGroup} objects (subgroups).
73 *
74 */
75 public static IQueryGroup of(IQueryGroup... subGroups) {
76 Set<IQuerySpecification<?>> patterns = new HashSet<IQuerySpecification<?>>();
77 for (IQueryGroup group : subGroups) {
78 patterns.addAll(group.getSpecifications());
79 }
80 return new GenericQueryGroup(patterns);
81 }
82}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java
new file mode 100644
index 00000000..5681ac19
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11import tools.refinery.viatra.runtime.api.impl.BaseQuerySpecification;
12import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
13import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility;
15
16/**
17 * This is a generic query specification for VIATRA pattern matchers, for "interpretative" query execution.
18 * Should be subclassed by query specification implementations specific to query languages.
19 *
20 * <p>
21 * When available, consider using the pattern-specific generated matcher API instead.
22 *
23 * <p>
24 * The created matcher will be of type {@link GenericPatternMatcher}. Matches of the pattern will be represented as
25 * {@link GenericPatternMatch}.
26 *
27 * <p>
28 * Note for overriding (if you have your own query language or ):
29 * Derived classes should use {@link #defaultInstantiate(ViatraQueryEngine)} for implementing
30 * {@link #instantiate(ViatraQueryEngine)} if they use {@link GenericPatternMatcher} proper.
31 *
32 * @see GenericPatternMatcher
33 * @see GenericPatternMatch
34 * @see GenericMatchProcessor
35 * @author Bergmann Gábor
36 * @noinstantiate This class is not intended to be instantiated by end-users.
37 * @since 0.9
38 */
39public abstract class GenericQuerySpecification<Matcher extends GenericPatternMatcher> extends
40 BaseQuerySpecification<Matcher> {
41
42 /**
43 * Instantiates query specification for the given internal query representation.
44 */
45 public GenericQuerySpecification(PQuery wrappedPQuery) {
46 super(wrappedPQuery);
47 }
48
49 @Override
50 public GenericPatternMatch newEmptyMatch() {
51 return GenericPatternMatch.newEmptyMatch(this);
52 }
53
54 @Override
55 public GenericPatternMatch newMatch(Object... parameters) {
56 return GenericPatternMatch.newMatch(this, parameters);
57 }
58
59 /**
60 * Derived classes should use this implementation of {@link #instantiate(ViatraQueryEngine)}
61 * if they use {@link GenericPatternMatcher} proper.
62 * @throws ViatraQueryRuntimeException
63 */
64 protected GenericPatternMatcher defaultInstantiate(ViatraQueryEngine engine) {
65 return GenericPatternMatcher.instantiate(engine, this);
66 }
67
68 /**
69 * @since 2.0
70 */
71 @Override
72 public PVisibility getVisibility() {
73 return getInternalQueryRepresentation().getVisibility();
74 }
75
76} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java
new file mode 100644
index 00000000..23c64537
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11/**
12 * An interface for low-level notifications about match appearance and disappearance.
13 *
14 * <p>
15 * See {@link ViatraQueryMatcher#addCallbackOnMatchUpdate(IMatchUpdateListener, boolean)} for usage. Clients should
16 * consider using {@link MatchUpdateAdapter} or deriving their implementation from it.
17 *
18 * @author Bergmann Gabor
19 *
20 */
21public interface IMatchUpdateListener<Match extends IPatternMatch> {
22 /**
23 * Will be invoked on each new match that appears.
24 *
25 * @param match
26 * the match that has just appeared.
27 */
28 public void notifyAppearance(Match match);
29
30 /**
31 * Will be invoked on each existing match that disappears.
32 *
33 * @param match
34 * the match that has just disappeared.
35 */
36 public void notifyDisappearance(Match match);
37}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java
new file mode 100644
index 00000000..be6467cc
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import java.util.List;
13
14/**
15 * Generic interface for a single match of a pattern. Each instance is a (partial) substitution of pattern parameters,
16 * essentially a parameter to value mapping.
17 *
18 * <p>Can also represent a partial match; unsubstituted parameters are assigned to null. Pattern matchers must never return
19 * a partial match, but they accept partial matches as method parameters.
20 *
21 * @author Bergmann Gábor
22 */
23public interface IPatternMatch extends Cloneable /* , Map<String, Object> */{
24 /** @return the pattern for which this is a match. */
25 public IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>> specification();
26
27 /** Identifies the name of the pattern for which this is a match. */
28 public String patternName();
29
30 /** Returns the list of symbolic parameter names. */
31 public List<String> parameterNames();
32
33 /** Returns the value of the parameter with the given name, or null if name is invalid. */
34 public Object get(String parameterName);
35
36 /** Returns the value of the parameter at the given position, or null if position is invalid. */
37 public Object get(int position);
38
39 /**
40 * Sets the parameter with the given name to the given value.
41 *
42 * <p> Works only if match is mutable. See {@link #isMutable()}.
43 *
44 * @returns true if successful, false if parameter name is invalid. May also fail and return false if the value type
45 * is incompatible.
46 * @throws UnsupportedOperationException if match is not mutable.
47 */
48 public boolean set(String parameterName, Object newValue);
49
50 /**
51 * Sets the parameter at the given position to the given value.
52 *
53 * <p> Works only if match is mutable. See {@link #isMutable()}.
54 *
55 * @returns true if successful, false if position is invalid. May also fail and return false if the value type is
56 * incompatible.
57 * @throws UnsupportedOperationException if match is not mutable.
58 */
59 public boolean set(int position, Object newValue);
60
61 /**
62 * Returns whether the match object can be further modified after its creation. Setters work only if the match is mutable.
63 *
64 * <p>Matches computed by the pattern matchers are not mutable, so that the match set cannot be modified externally.
65 * Partial matches used as matcher input, however, can be mutable; such match objects can be created using {@link ViatraQueryMatcher#newEmptyMatch()}.
66 *
67 * @return whether the match can be modified
68 */
69 public boolean isMutable();
70
71 /**
72 * Converts the match to an array representation, with each pattern parameter at their respective position.
73 * In case of a partial match, unsubstituted parameters will be represented as null elements in the array.
74 *
75 * @return a newly constructed array containing each parameter substitution of the match in order.
76 */
77 public Object[] toArray();
78
79 /**
80 * Takes an immutable snapshot of this match.
81 * @return the match itself in case of immutable matches, an immutable copy in case of mutable ones.
82 */
83 public IPatternMatch toImmutable();
84
85 /** Prints the list of parameter-value pairs. */
86 public String prettyPrint();
87
88 /**
89 * Checks that this match is compatible with the given other match.
90 * This is used for filtering the match set of a matcher.
91 *
92 * <p/> Two non-null matches are compatible if and only if:
93 * <ul>
94 * <li>They share the same pattern.</li>
95 * <li>For each parameter, where they are set (non-null) in both matches,
96 * their values are equal.</li>
97 * </li>
98 * </ul>
99 *
100 * <p/> Furthermore, all matches are considered compatible with
101 * null matches (e.g. empty filter).
102 *
103 * @param other
104 * @return true, if this is compatible with other, or other is null
105 */
106 public boolean isCompatibleWith(IPatternMatch other);
107}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java
new file mode 100644
index 00000000..a783f823
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Mark Czotter, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11import java.util.Set;
12
13/**
14 * Generic interface for group of query specifications.
15 *
16 * <p>It handles more than one patterns as a group, and provides functionality to initialize the matchers together (which
17 * has performance benefits).
18 *
19 * @author Mark Czotter
20 *
21 */
22public interface IQueryGroup {
23
24 /**
25 * Initializes matchers for the group of patterns within an {@link ViatraQueryEngine}. If some of the pattern matchers are already
26 * constructed in the engine, no task is performed for them.
27 *
28 * <p>
29 * This preparation step has the advantage that it prepares pattern matchers for an arbitrary number of patterns in a
30 * single-pass traversal of the model.
31 * This is typically more efficient than traversing the model each time an individual pattern matcher is initialized on demand.
32 * The performance benefit only manifests itself if the engine is not in wildcard mode.
33 *
34 * @param engine
35 * the existing VIATRA Query engine in which the matchers will be created.
36 * @throws ViatraQueryRuntimeException
37 * if there was an error in preparing the engine
38 */
39 public void prepare(ViatraQueryEngine engine);
40
41 /**
42 * Returns the currently assigned {@link IQuerySpecification}s.
43 */
44 public Set<IQuerySpecification<?>> getSpecifications();
45
46}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java
new file mode 100644
index 00000000..326d2202
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import tools.refinery.viatra.runtime.api.scope.QueryScope;
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueryHeader;
16
17/**
18 * API interface for a VIATRA query specification. Each query is associated with a pattern. Methods instantiate a matcher
19 * of the pattern with various parameters.
20 *
21 * <p> As of 0.9.0, some internal details (mostly relevant for query evaluator backends) have been moved to {@link #getInternalQueryRepresentation()}.
22 *
23 * @author Bergmann Gábor
24 *
25 */
26public interface IQuerySpecification<Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> extends PQueryHeader {
27
28 /**
29 * Initializes the pattern matcher within an existing {@link ViatraQueryEngine}. If the pattern matcher is already
30 * constructed in the engine, only a lightweight reference is created.
31 * <p>
32 * The match set will be incrementally refreshed upon updates.
33 *
34 * @param engine
35 * the existing VIATRA Query engine in which this matcher will be created.
36 * @throws ViatraQueryRuntimeException
37 * if an error occurs during pattern matcher creation
38 */
39 public Matcher getMatcher(ViatraQueryEngine engine);
40
41
42 /**
43 * Returns an empty, mutable Match compatible with matchers of this query.
44 * Fields of the mutable match can be filled to create a partial match, usable as matcher input.
45 * This can be used to call the matcher with a partial match
46 * even if the specific class of the matcher or the match is unknown.
47 *
48 * @return the empty match
49 */
50 public abstract IPatternMatch newEmptyMatch();
51
52 /**
53 * Returns a new (partial) Match object compatible with matchers of this query.
54 * This can be used e.g. to call the matcher with a partial
55 * match.
56 *
57 * <p>The returned match will be immutable. Use {@link #newEmptyMatch()} to obtain a mutable match object.
58 *
59 * @param parameters
60 * the fixed value of pattern parameters, or null if not bound.
61 * @return the (partial) match object.
62 */
63 public abstract IPatternMatch newMatch(Object... parameters);
64
65 /**
66 * The query is formulated over this kind of modeling platform.
67 * E.g. for queries over EMF models, the {@link EMFScope} class is returned.
68 */
69 public Class<? extends QueryScope> getPreferredScopeClass();
70
71 /**
72 * Returns the definition of the query in a format intended for consumption by the query evaluator.
73 * @return the internal representation of the query.
74 */
75 public PQuery getInternalQueryRepresentation();
76
77 /**
78 * Creates a new uninitialized matcher, which is not functional until an engine initializes it. Clients
79 * should not call this method, it is used by the {@link ViatraQueryEngine} instance to instantiate matchers.
80 * @throws ViatraQueryRuntimeException
81 * @noreference This method is not intended to be referenced by clients.
82 * @since 1.4
83 */
84 public Matcher instantiate();
85
86}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java
new file mode 100644
index 00000000..7de6d2c6
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11import java.util.function.Consumer;
12
13/**
14 * A default implementation of {@link IMatchUpdateListener} that contains two match processors, one for appearance, one
15 * for disappearance. Any of the two can be null; in this case, corresponding notifications will be ignored.
16 *
17 * <p>
18 * Instantiate using either constructor.
19 *
20 * @author Bergmann Gabor
21 *
22 */
23public class MatchUpdateAdapter<Match extends IPatternMatch> implements IMatchUpdateListener<Match> {
24
25 Consumer<Match> appearCallback;
26 Consumer<Match> disappearCallback;
27
28 /**
29 * Constructs an instance without any match processors registered yet.
30 *
31 * Use {@link #setAppearCallback(Consumer)} and {@link #setDisappearCallback(Consumer)} to specify
32 * optional match processors for match appearance and disappearance, respectively.
33 */
34 public MatchUpdateAdapter() {
35 super();
36 }
37
38 /**
39 * Constructs an instance by specifying match processors.
40 *
41 * @param appearCallback
42 * a match processor that will be invoked on each new match that appears. If null, no callback will be
43 * executed on match appearance. See {@link Consumer} for details on how to implement.
44 * @param disappearCallback
45 * a match processor that will be invoked on each existing match that disappears. If null, no callback
46 * will be executed on match disappearance. See {@link Consumer} for details on how to implement.
47 * @since 2.0
48 */
49 public MatchUpdateAdapter(Consumer<Match> appearCallback, Consumer<Match> disappearCallback) {
50 super();
51 setAppearCallback(appearCallback);
52 setDisappearCallback(disappearCallback);
53 }
54
55 /**
56 * @return the match processor that will be invoked on each new match that appears. If null, no callback will be
57 * executed on match appearance.
58 * @since 2.0
59 */
60 public Consumer<Match> getAppearCallback() {
61 return appearCallback;
62 }
63
64 /**
65 * @param appearCallback
66 * a match processor that will be invoked on each new match that appears. If null, no callback will be
67 * executed on match appearance. See {@link Consumer} for details on how to implement.
68 * @since 2.0
69 */
70 public void setAppearCallback(Consumer<Match> appearCallback) {
71 this.appearCallback = appearCallback;
72 }
73
74 /**
75 * @return the match processor that will be invoked on each existing match that disappears. If null, no callback
76 * will be executed on match disappearance.
77 * @since 2.0
78 */
79 public Consumer<Match> getDisappearCallback() {
80 return disappearCallback;
81 }
82
83 /**
84 * @param disappearCallback
85 * a match processor that will be invoked on each existing match that disappears. If null, no callback
86 * will be executed on match disappearance. See {@link Consumer} for details on how to implement.
87 * @since 2.0
88 */
89 public void setDisappearCallback(Consumer<Match> disappearCallback) {
90 this.disappearCallback = disappearCallback;
91 }
92
93 @Override
94 public void notifyAppearance(Match match) {
95 if (appearCallback != null)
96 appearCallback.accept(match);
97 }
98
99 @Override
100 public void notifyDisappearance(Match match) {
101 if (disappearCallback != null)
102 disappearCallback.accept(match);
103 }
104
105}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java
new file mode 100644
index 00000000..4c603a47
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java
@@ -0,0 +1,150 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10
11package tools.refinery.viatra.runtime.api;
12
13import tools.refinery.viatra.runtime.api.scope.IBaseIndex;
14import tools.refinery.viatra.runtime.api.scope.QueryScope;
15import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
16
17import java.util.Set;
18import java.util.stream.Collectors;
19
20/**
21 * A Viatra Query (incremental) evaluation engine, attached to a model such as an EMF resource. The engine hosts pattern matchers, and
22 * will listen on model update notifications stemming from the given model in order to maintain live results.
23 *
24 * <p>
25 * By default, ViatraQueryEngines do not need to be separately disposed; they will be garbage collected along with the model.
26 * Advanced users: see {@link AdvancedViatraQueryEngine} if you want fine control over the lifecycle of an engine.
27 *
28 * <p>
29 * Pattern matchers within this engine may be instantiated in the following ways:
30 * <ul>
31 * <li>Recommended: instantiate the specific matcher class generated for the pattern by e.g. MyPatternMatcher.on(engine).
32 * <li>Use {@link #getMatcher(IQuerySpecification)} if the pattern-specific generated matcher API is not available.
33 * <li>Advanced: use the query specification associated with the generated matcher class to achieve the same.
34 * </ul>
35 * Additionally, a group of patterns (see {@link IQueryGroup}) can be initialized together before usage; this may improve
36 * the performance of pattern matcher construction by trying to gather all necessary information from the model in one go.
37 * Note that no such improvement is to be expected if the engine is specifically constructed in wildcard mode,
38 * an option available in some scope implementations
39 * (see {@link EMFScope#EMFScope(Notifier, BaseIndexOptions)} and {@link BaseIndexOptions#withWildcardMode(boolean)}).
40 *
41 *
42 * @author Bergmann Gábor
43 * @noextend This class is not intended to be subclassed by clients.
44 */
45public abstract class ViatraQueryEngine {
46
47
48 /**
49 * Obtain a (managed) {@link ViatraQueryEngine} to evaluate queries over a given scope specified by an {@link QueryScope}.
50 *
51 * <p> For a given matcher scope, the same engine will be returned to any client.
52 * This facilitates the reuse of internal caches of the engine, greatly improving performance.
53 *
54 * <p> The lifecycle of this engine is centrally managed, and will not be disposed as long as the model is retained in memory.
55 * The engine will be garbage collected along with the model.
56 *
57 * <p>
58 * Advanced users: see {@link AdvancedViatraQueryEngine#createUnmanagedEngine(QueryScope)} to obtain a private,
59 * unmanaged engine that is not shared with other clients and allows tight control over its lifecycle.
60 *
61 * @param scope
62 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
63 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
64 * @return a (managed) {@link ViatraQueryEngine} instance
65 */
66 public static ViatraQueryEngine on(QueryScope scope) {
67 return ViatraQueryEngineManager.getInstance().getQueryEngine(scope);
68 }
69
70 /**
71 * Obtain a (managed) {@link ViatraQueryEngine} to evaluate queries over a given scope specified by an {@link QueryScope}.
72 *
73 * <p> For a given matcher scope, the same engine will be returned to any client.
74 * This facilitates the reuse of internal caches of the engine, greatly improving performance.
75 *
76 * <p> The lifecycle of this engine is centrally managed, and will not be disposed as long as the model is retained in memory.
77 * The engine will be garbage collected along with the model.
78 *
79 * <p>
80 * Advanced users: see {@link AdvancedViatraQueryEngine#createUnmanagedEngine(QueryScope)} to obtain a private,
81 * unmanaged engine that is not shared with other clients and allows tight control over its lifecycle.
82 *
83 * @param scope
84 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
85 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
86 * @return a (managed) {@link ViatraQueryEngine} instance
87 * @since 1.4
88 */
89 public static ViatraQueryEngine on(QueryScope scope, ViatraQueryEngineOptions options) {
90 return ViatraQueryEngineManager.getInstance().getQueryEngine(scope, options);
91 }
92
93 /**
94 * Provides access to the internal base index component of the engine, responsible for keeping track of basic
95 * contents of the model.
96 *
97 * <p>If using an {@link EMFScope},
98 * consider {@link EMFScope#extractUnderlyingEMFIndex(ViatraQueryEngine)} instead to access EMF-specific details.
99 *
100 * @return the baseIndex the NavigationHelper maintaining the base index
101 * @throws ViatraQueryRuntimeException
102 * if the base index could not be constructed
103 */
104 public abstract IBaseIndex getBaseIndex();
105
106 /**
107 * Access a pattern matcher based on a {@link IQuerySpecification}.
108 * Multiple calls will return the same matcher.
109 * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification
110 * @return a pattern matcher corresponding to the specification
111 * @throws ViatraQueryRuntimeException if the matcher could not be initialized
112 */
113 public abstract <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getMatcher(IQuerySpecification<Matcher> querySpecification);
114
115 /**
116 * Access a pattern matcher for the graph pattern with the given fully qualified name.
117 * Will succeed only if a query specification for this fully qualified name has been generated and registered.
118 * Multiple calls will return the same matcher unless the registered specification changes.
119 *
120 * @param patternFQN the fully qualified name of a VIATRA query specification
121 * @return a pattern matcher corresponding to the specification
122 * @throws ViatraQueryRuntimeException if the matcher could not be initialized
123 */
124 public abstract ViatraQueryMatcher<? extends IPatternMatch> getMatcher(String patternFQN);
125
126 /**
127 * Access an existing pattern matcher based on a {@link IQuerySpecification}.
128 * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification
129 * @return a pattern matcher corresponding to the specification, <code>null</code> if a matcher does not exist yet.
130 */
131 public abstract <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getExistingMatcher(IQuerySpecification<Matcher> querySpecification);
132
133
134 /**
135 * Access a copy of available {@link ViatraQueryMatcher} pattern matchers.
136 * @return a copy of the set of currently available pattern matchers registered on this engine instance
137 */
138 public abstract Set<? extends ViatraQueryMatcher<? extends IPatternMatch>> getCurrentMatchers();
139
140 public Set<IQuerySpecification<? extends ViatraQueryMatcher<? extends IPatternMatch>>> getRegisteredQuerySpecifications() {
141 return getCurrentMatchers().stream().map(ViatraQueryMatcher::getSpecification).collect(Collectors.toSet());
142 }
143
144 /**
145 * @return the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
146 */
147 public abstract QueryScope getScope();
148
149 public abstract void flushChanges();
150}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java
new file mode 100644
index 00000000..02162a65
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11/**
12 * Listener interface to get notifications when a new managed engine is initialized.
13 *
14 * @author Abel Hegedus
15 *
16 */
17public interface ViatraQueryEngineInitializationListener {
18
19 /**
20 * Called when a managed engine is initialized in the EngineManager.
21 *
22 * @param engine the initialized engine
23 */
24 void engineInitialized(AdvancedViatraQueryEngine engine);
25
26}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java
new file mode 100644
index 00000000..1c4dc264
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java
@@ -0,0 +1,52 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11
12/**
13 * Listener interface for getting notification on changes in an {@link ViatraQueryEngine}.
14 *
15 * You can use it to remove any other listeners that you attached to matchers or the engine,
16 * or to handle matchers that are initialized after you started using the engine.
17 *
18 * @author Abel Hegedus
19 *
20 */
21public interface ViatraQueryEngineLifecycleListener {
22
23 // -------------------------------------------------------------------------------
24 // MATCHERS (methods notifying on changes in the matchers available in the engine)
25 // -------------------------------------------------------------------------------
26
27 /**
28 * Called after a matcher is instantiated in the engine
29 *
30 * @param matcher the new matcher
31 */
32 void matcherInstantiated(ViatraQueryMatcher<? extends IPatternMatch> matcher);
33
34 // -------------------------------------------------------------------------
35 // HEALTH (methods notifying on changes that affect the health of the engine
36 // -------------------------------------------------------------------------
37
38 /**
39 * Called after the engine has become tainted due to a fatal error
40 */
41 void engineBecameTainted(String message, Throwable t);
42
43 /**
44 * Called after the engine has been wiped
45 */
46 void engineWiped();
47
48 /**
49 * Called after the engine has been disposed
50 */
51 void engineDisposed();
52}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java
new file mode 100644
index 00000000..4a256aea
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java
@@ -0,0 +1,191 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import tools.refinery.viatra.runtime.api.scope.QueryScope;
13import tools.refinery.viatra.runtime.internal.apiimpl.ViatraQueryEngineImpl;
14import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil;
15
16import java.lang.ref.WeakReference;
17import java.util.*;
18
19import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument;
20
21/**
22 * Global registry of active VIATRA Query Engines.
23 *
24 * <p>
25 * Manages an {@link ViatraQueryEngine} for each model (more precisely scope), that is created on demand. Managed engines are shared between
26 * clients querying the same model.
27 *
28 * <p>
29 * It is also possible to create private, unmanaged engines that are not shared between clients.
30 *
31 * <p>
32 * Only weak references are retained on the managed engines. So if there are no other references to the matchers or the
33 * engine, they can eventually be GC'ed, and they won't block the model from being GC'ed either.
34 *
35 *
36 * @author Bergmann Gabor
37 *
38 */
39public class ViatraQueryEngineManager {
40 private static ViatraQueryEngineManager instance = new ViatraQueryEngineManager();
41
42
43 /**
44 * @return the singleton instance
45 */
46 public static ViatraQueryEngineManager getInstance() {
47 return instance;
48 }
49
50 /**
51 * The engine manager keeps track of the managed engines via weak references only, so it will not keep unused
52 * managed engines from being garbage collected. Still, as long as the user keeps the model in memory, the
53 * associated managed engine will not be garbage collected, as it is expected to be strongly reachable from the
54 * model via the scope-specific base index or change notification listeners (see
55 * {@link BaseIndexListener#iqEngine}).
56 */
57 Map<QueryScope, WeakReference<ViatraQueryEngineImpl>> engines;
58
59 ViatraQueryEngineManager() {
60 super();
61 engines = new WeakHashMap<QueryScope, WeakReference<ViatraQueryEngineImpl>>();
62 initializationListeners = new HashSet<ViatraQueryEngineInitializationListener>();
63 }
64
65 /**
66 * Creates a managed VIATRA Query Engine at a given scope (e.g. an EMF Resource or ResourceSet, as in {@link EMFScope})
67 * or retrieves an already existing one. Repeated invocations for a single model root will return the same engine.
68 * Consequently, the engine will be reused between different clients querying the same model, providing performance benefits.
69 *
70 * <p>
71 * The match set of any patterns will be incrementally refreshed upon updates from this scope.
72 *
73 * @param scope
74 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
75 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
76 * @return a new or previously existing engine
77 */
78 public ViatraQueryEngine getQueryEngine(QueryScope scope) {
79 return getQueryEngine(scope, ViatraQueryEngineOptions.getDefault());
80 }
81
82 /**
83 * Creates a managed VIATRA Query Engine at a given scope (e.g. an EMF Resource or ResourceSet, as in {@link EMFScope})
84 * or retrieves an already existing one. Repeated invocations for a single model root will return the same engine.
85 * Consequently, the engine will be reused between different clients querying the same model, providing performance benefits.
86 *
87 * <p>
88 * The match set of any patterns will be incrementally refreshed upon updates from this scope.
89 *
90 * @param scope
91 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
92 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
93 * @return a new or previously existing engine
94 * @since 1.4
95 */
96 public ViatraQueryEngine getQueryEngine(QueryScope scope, ViatraQueryEngineOptions options) {
97 ViatraQueryEngineImpl engine = getEngineInternal(scope);
98 if (engine == null) {
99 engine = new ViatraQueryEngineImpl(this, scope, options);
100 engines.put(scope, new WeakReference<ViatraQueryEngineImpl>(engine));
101 notifyInitializationListeners(engine);
102 }
103 return engine;
104 }
105
106 /**
107 * Retrieves an already existing managed VIATRA Query Engine.
108 *
109 * @param scope
110 * the scope of query evaluation; the definition of the set of model elements that this engine is operates on.
111 * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model.
112 * @return a previously existing engine, or null if no engine is active for the given EMF model root
113 */
114 public ViatraQueryEngine getQueryEngineIfExists(QueryScope scope) {
115 return getEngineInternal(scope);
116 }
117
118 /**
119 * Collects all {@link ViatraQueryEngine} instances that still exist.
120 *
121 * @return set of engines if there is any, otherwise EMTPY_SET
122 */
123 public Set<ViatraQueryEngine> getExistingQueryEngines(){
124 Set<ViatraQueryEngine> existingEngines = null;
125 for (WeakReference<ViatraQueryEngineImpl> engineRef : engines.values()) {
126 AdvancedViatraQueryEngine engine = engineRef == null ? null : engineRef.get();
127 if(existingEngines == null) {
128 existingEngines = new HashSet<>();
129 }
130 existingEngines.add(engine);
131 }
132 if(existingEngines == null) {
133 existingEngines = Collections.emptySet();
134 }
135 return existingEngines;
136 }
137
138 private final Set<ViatraQueryEngineInitializationListener> initializationListeners;
139
140 /**
141 * Registers a listener for new engine initialization.
142 *
143 * <p/> For removal, use {@link #removeQueryEngineInitializationListener}
144 *
145 * @param listener the listener to register
146 */
147 public void addQueryEngineInitializationListener(ViatraQueryEngineInitializationListener listener) {
148 checkArgument(listener != null, "Cannot add null listener!");
149 initializationListeners.add(listener);
150 }
151
152 /**
153 * Removes a registered listener added with {@link #addQueryEngineInitializationListener}
154 *
155 * @param listener
156 */
157 public void removeQueryEngineInitializationListener(ViatraQueryEngineInitializationListener listener) {
158 checkArgument(listener != null, "Cannot remove null listener!");
159 initializationListeners.remove(listener);
160 }
161
162 /**
163 * Notifies listeners that a new engine has been initialized.
164 *
165 * @param engine the initialized engine
166 */
167 protected void notifyInitializationListeners(AdvancedViatraQueryEngine engine) {
168 try {
169 if (!initializationListeners.isEmpty()) {
170 for (ViatraQueryEngineInitializationListener listener : new HashSet<>(initializationListeners)) {
171 listener.engineInitialized(engine);
172 }
173 }
174 } catch (Exception ex) {
175 ViatraQueryLoggingUtil.getLogger(getClass()).fatal(
176 "VIATRA Query Engine Manager encountered an error in delivering notifications"
177 + " about engine initialization. ", ex);
178 }
179 }
180
181 /**
182 * @param emfRoot
183 * @return
184 */
185 private ViatraQueryEngineImpl getEngineInternal(QueryScope scope) {
186 final WeakReference<ViatraQueryEngineImpl> engineRef = engines.get(scope);
187 ViatraQueryEngineImpl engine = engineRef == null ? null : engineRef.get();
188 return engine;
189 }
190
191}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java
new file mode 100644
index 00000000..15bf1f91
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java
@@ -0,0 +1,293 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Balázs Grill, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11
12import java.util.Objects;
13import java.util.ServiceLoader;
14
15import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
16import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider;
17import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
18import tools.refinery.viatra.runtime.matchers.util.Preconditions;
19
20/**
21 * This class is intended to provide options to a created {@link ViatraQueryEngine} instance. The {@link #DEFAULT}
22 * instance represents the configuration that is selected when no explicit options are provided by the user. To create
23 * new configurations, use the static builder methods {@link #defineOptions()} (starts with empty options) or
24 * {@link #copyOptions(ViatraQueryEngineOptions)} (starts with all options from an existing configuration).
25 *
26 * @author Balázs Grill, Zoltan Ujhelyi
27 * @since 1.4
28 *
29 */
30public final class ViatraQueryEngineOptions {
31
32 private static boolean areSystemDefaultsCalculated = false;
33 private static IQueryBackendFactory systemDefaultBackendFactory;
34 private static IQueryBackendFactory systemDefaultCachingBackendFactory;
35 private static IQueryBackendFactory systemDefaultSearchBackendFactory;
36
37 /**
38 * @since 2.0
39 */
40 public static void setSystemDefaultBackends(IQueryBackendFactory systemDefaultBackendFactory,
41 IQueryBackendFactory systemDefaultCachingBackendFactory,
42 IQueryBackendFactory systemDefaultSearchBackendFactory) {
43 areSystemDefaultsCalculated = true;
44 ViatraQueryEngineOptions.systemDefaultBackendFactory = systemDefaultBackendFactory;
45 ViatraQueryEngineOptions.systemDefaultCachingBackendFactory = systemDefaultCachingBackendFactory;
46 ViatraQueryEngineOptions.systemDefaultSearchBackendFactory = systemDefaultSearchBackendFactory;
47 }
48
49 /**
50 * If {@link #setSystemDefaultBackends(IQueryBackendFactory, IQueryBackendFactory, IQueryBackendFactory)} is not
51 * called, this method is responsible of finding the corresponding backends on the classpath using Java Service
52 * loaders.
53 */
54 private static void calculateSystemDefaultBackends() {
55 for (IQueryBackendFactoryProvider provider : ServiceLoader.load(IQueryBackendFactoryProvider.class)) {
56 if (provider.isSystemDefaultEngine()) {
57 systemDefaultBackendFactory = provider.getFactory();
58 }
59 if (provider.isSystemDefaultCachingBackend()) {
60 systemDefaultCachingBackendFactory = provider.getFactory();
61 }
62 if (provider.isSystemDefaultSearchBackend()) {
63 systemDefaultSearchBackendFactory = provider.getFactory();
64 }
65 }
66 areSystemDefaultsCalculated = true;
67 }
68
69 private static IQueryBackendFactory getSystemDefaultBackend() {
70 if (!areSystemDefaultsCalculated) {
71 calculateSystemDefaultBackends();
72 }
73 return Objects.requireNonNull(systemDefaultBackendFactory, "System default backend not found");
74 }
75
76 private static IQueryBackendFactory getSystemDefaultCachingBackend() {
77 if (!areSystemDefaultsCalculated) {
78 calculateSystemDefaultBackends();
79 }
80 return Objects.requireNonNull(systemDefaultCachingBackendFactory, "System default caching backend not found");
81 }
82
83 private static IQueryBackendFactory getSystemDefaultSearchBackend() {
84 if (!areSystemDefaultsCalculated) {
85 calculateSystemDefaultBackends();
86 }
87 return Objects.requireNonNull(systemDefaultSearchBackendFactory, "System default search backend not found");
88 }
89
90 private final QueryEvaluationHint engineDefaultHints;
91
92 private final IQueryBackendFactory defaultCachingBackendFactory;
93 private final IQueryBackendFactory defaultSearchBackendFactory;
94
95 /** The default engine options; if options are not defined, this version will be used. */
96 private static ViatraQueryEngineOptions DEFAULT;
97
98 /**
99 * @since 2.0
100 */
101 public static final ViatraQueryEngineOptions getDefault() {
102 if (DEFAULT == null) {
103 DEFAULT = new Builder().build();
104 }
105 return DEFAULT;
106 }
107
108 public static final class Builder {
109 private QueryEvaluationHint engineDefaultHints;
110
111 private IQueryBackendFactory defaultBackendFactory;
112 private IQueryBackendFactory defaultCachingBackendFactory;
113 private IQueryBackendFactory defaultSearchBackendFactory;
114
115 public Builder() {
116
117 }
118
119 public Builder(ViatraQueryEngineOptions from) {
120 this.engineDefaultHints = from.engineDefaultHints;
121 this.defaultBackendFactory = engineDefaultHints.getQueryBackendFactory();
122 this.defaultCachingBackendFactory = from.defaultCachingBackendFactory;
123 this.defaultSearchBackendFactory = from.defaultSearchBackendFactory;
124 }
125
126 /**
127 * Note that the backend factory in the hint is overridden by a factory added with
128 * {@link #withDefaultBackend(IQueryBackendFactory)}.
129 */
130 public Builder withDefaultHint(QueryEvaluationHint engineDefaultHints) {
131 this.engineDefaultHints = engineDefaultHints;
132 return this;
133 }
134
135 /**
136 * Note that this backend factory overrides the factory defined by the hint added by
137 * {@link #withDefaultHint(QueryEvaluationHint)}.
138 */
139 public Builder withDefaultBackend(IQueryBackendFactory defaultBackendFactory) {
140 this.defaultBackendFactory = defaultBackendFactory;
141 return this;
142 }
143
144 /**
145 * @since 2.0
146 */
147 public Builder withDefaultSearchBackend(IQueryBackendFactory defaultSearchBackendFactory) {
148 Preconditions.checkArgument(!defaultSearchBackendFactory.isCaching(), "%s is not a search backend", defaultSearchBackendFactory.getClass());
149 this.defaultSearchBackendFactory = defaultSearchBackendFactory;
150 return this;
151 }
152
153 public Builder withDefaultCachingBackend(IQueryBackendFactory defaultCachingBackendFactory) {
154 Preconditions.checkArgument(defaultCachingBackendFactory.isCaching(), "%s is not a caching backend", defaultCachingBackendFactory.getClass());
155 this.defaultCachingBackendFactory = defaultCachingBackendFactory;
156 return this;
157 }
158
159 public ViatraQueryEngineOptions build() {
160 IQueryBackendFactory defaultFactory = getDefaultBackend();
161 QueryEvaluationHint hint = getEngineDefaultHints(defaultFactory);
162 return new ViatraQueryEngineOptions(hint, getDefaultCachingBackend(), getDefaultSearchBackend());
163 }
164
165 private IQueryBackendFactory getDefaultBackend() {
166 if (defaultBackendFactory != null){
167 return defaultBackendFactory;
168 } else if (engineDefaultHints != null) {
169 return engineDefaultHints.getQueryBackendFactory();
170 } else {
171 return getSystemDefaultBackend();
172 }
173 }
174
175 private IQueryBackendFactory getDefaultCachingBackend() {
176 if (defaultCachingBackendFactory != null) {
177 return defaultCachingBackendFactory;
178 } else if (defaultBackendFactory != null && defaultBackendFactory.isCaching()) {
179 return defaultBackendFactory;
180 } else {
181 return getSystemDefaultCachingBackend();
182 }
183 }
184
185 private IQueryBackendFactory getDefaultSearchBackend() {
186 if (defaultSearchBackendFactory != null) {
187 return defaultSearchBackendFactory;
188 } else if (defaultBackendFactory != null && !defaultBackendFactory.isCaching()) {
189 return defaultBackendFactory;
190 } else {
191 return getSystemDefaultSearchBackend();
192 }
193 }
194
195 private QueryEvaluationHint getEngineDefaultHints(IQueryBackendFactory defaultFactory) {
196 if (engineDefaultHints != null){
197 return engineDefaultHints.overrideBy(new QueryEvaluationHint(null, defaultFactory));
198 } else {
199 return new QueryEvaluationHint(null, defaultFactory);
200 }
201 }
202 }
203
204 /**
205 * Initializes an option builder with no previously set options.
206 */
207 public static Builder defineOptions() {
208 return new Builder();
209 }
210
211 /**
212 * Initializes an option builder with settings from an existing configuration.
213 */
214 public static Builder copyOptions(ViatraQueryEngineOptions options) {
215 return new Builder(options);
216 }
217
218 private ViatraQueryEngineOptions(QueryEvaluationHint engineDefaultHints,
219 IQueryBackendFactory defaultCachingBackendFactory, IQueryBackendFactory defaultSearchBackendFactory) {
220 this.engineDefaultHints = engineDefaultHints;
221 this.defaultCachingBackendFactory = defaultCachingBackendFactory;
222 this.defaultSearchBackendFactory = defaultSearchBackendFactory;
223 }
224
225 public QueryEvaluationHint getEngineDefaultHints() {
226 return engineDefaultHints;
227 }
228
229 /**
230 * Returns the configured default backend
231 *
232 * @return the defaultBackendFactory
233 */
234 public IQueryBackendFactory getDefaultBackendFactory() {
235 switch (engineDefaultHints.getQueryBackendRequirementType()) {
236 case DEFAULT_CACHING:
237 return ViatraQueryEngineOptions.getSystemDefaultCachingBackend();
238 case DEFAULT_SEARCH:
239 return ViatraQueryEngineOptions.getSystemDefaultCachingBackend();
240 case SPECIFIC:
241 return engineDefaultHints.getQueryBackendFactory();
242 case UNSPECIFIED:
243 default:
244 return ViatraQueryEngineOptions.getSystemDefaultBackend();
245 }
246 }
247
248 /**
249 * Returns the configured default caching backend. If the default backend caches matches, it is usually expected, but
250 * not mandatory for the two default backends to be the same.
251 */
252 public IQueryBackendFactory getDefaultCachingBackendFactory() {
253 return defaultCachingBackendFactory;
254 }
255
256 /**
257 * Returns the configured default search-based backend. If the default backend is search-based, it is usually expected, but
258 * not mandatory for the two default backends to be the same.
259 * @since 2.0
260 */
261 public IQueryBackendFactory getDefaultSearchBackendFactory() {
262 return defaultSearchBackendFactory;
263 }
264
265 @Override
266 public String toString() {
267 // TODO defaultCachingBackendFactory is ignored
268 if(Objects.equals(engineDefaultHints, DEFAULT.engineDefaultHints))
269 return "defaults";
270 else
271 return engineDefaultHints.toString();
272 }
273
274 /**
275 * @since 2.0
276 */
277 public IQueryBackendFactory getQueryBackendFactory(QueryEvaluationHint hint) {
278 if (hint == null) {
279 return getDefaultBackendFactory();
280 }
281
282 switch (hint.getQueryBackendRequirementType()) {
283 case DEFAULT_CACHING:
284 return getDefaultCachingBackendFactory();
285 case DEFAULT_SEARCH:
286 return getDefaultSearchBackendFactory();
287 case SPECIFIC:
288 return hint.getQueryBackendFactory();
289 default:
290 return getDefaultBackendFactory();
291 }
292 }
293}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java
new file mode 100644
index 00000000..1ae0c96f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java
@@ -0,0 +1,258 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api;
11
12import java.util.Collection;
13import java.util.List;
14import java.util.Optional;
15import java.util.Set;
16import java.util.function.Consumer;
17import java.util.stream.Stream;
18
19/**
20 * Interface for a VIATRA Query matcher associated with a graph pattern.
21 *
22 * @param <Match>
23 * the IPatternMatch type representing a single match of this pattern.
24 * @author Bergmann Gábor
25 * @noimplement This interface is not intended to be implemented by clients. Implement BaseMatcher instead.
26 */
27public interface ViatraQueryMatcher<Match extends IPatternMatch> {
28 // REFLECTION
29 /** The pattern that will be matched. */
30 IQuerySpecification<? extends ViatraQueryMatcher<Match>> getSpecification();
31
32 /** Fully qualified name of the pattern. */
33 String getPatternName();
34
35 /** Returns the index of the symbolic parameter with the given name. */
36 Integer getPositionOfParameter(String parameterName);
37
38 /** Returns the array of symbolic parameter names. */
39 List<String> getParameterNames();
40
41 // ALL MATCHES
42 /**
43 * Returns the set of all pattern matches.
44 *
45 * @return matches represented as a Match object.
46 */
47 Collection<Match> getAllMatches();
48
49 /**
50 * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters.
51 *
52 * @param partialMatch
53 * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to
54 * a fixed value.
55 * @return matches represented as a Match object.
56 */
57 Collection<Match> getAllMatches(Match partialMatch);
58
59 /**
60 * Returns a stream of all pattern matches.
61 * <p>
62 * <strong>WARNING</strong> If the result set changes while the stream is evaluated, the set of matches included in
63 * the stream are unspecified. In such cases, either rely on {@link #getAllMatches()} or collect the results of the
64 * stream in end-user code.
65 *
66 * @return matches represented as a Match object.
67 * @since 2.0
68 */
69 Stream<Match> streamAllMatches();
70
71 /**
72 * Returns a stream of all matches of the pattern that conform to the given fixed values of some parameters.
73 * <p>
74 * <strong>WARNING</strong> If the result set changes while the stream is evaluated, the set of matches included in
75 * the stream are unspecified. In such cases, either rely on {@link #getAllMatches()} or collect the results of the
76 * stream in end-user code.
77 *
78 * @param partialMatch
79 * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to
80 * a fixed value.
81 * @return matches represented as a Match object.
82 * @since 2.0
83 */
84 Stream<Match> streamAllMatches(Match partialMatch);
85
86 // variant(s) with input binding as pattern-specific parameters: not declared in interface
87
88 // SINGLE MATCH
89 /**
90 * Returns an arbitrarily chosen pattern match. Neither determinism nor randomness of selection is guaranteed.
91 *
92 * @return a match represented as a Match object, or an empty Optional if no match is found.
93 * @since 2.0
94 */
95 Optional<Match> getOneArbitraryMatch();
96
97 /**
98 * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters.
99 * Neither determinism nor randomness of selection is guaranteed.
100 *
101 * @param partialMatch
102 * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to
103 * a fixed value.
104 * @return a match represented as a Match object, or an empty Optional if no match is found.
105 * @since 2.0
106 */
107 Optional<Match> getOneArbitraryMatch(Match partialMatch);
108
109 // variant(s) with input binding as pattern-specific parameters: not declared in interface
110
111 // MATCH CHECKING
112 /**
113 * Indicates whether the query has any kind of matches.
114 *
115 * @return true if there exists a valid match of the pattern.
116 * @since 1.7
117 */
118 boolean hasMatch();
119
120 /**
121 * Indicates whether the given combination of specified pattern parameters constitute a valid pattern match, under
122 * any possible substitution of the unspecified parameters (if any).
123 *
124 * @param partialMatch
125 * a (partial) match of the pattern where each non-null field binds the corresponding pattern parameter
126 * to a fixed value.
127 * @return true if the input is a valid (partial) match of the pattern.
128 */
129 boolean hasMatch(Match partialMatch);
130
131 // variant(s) with input binding as pattern-specific parameters: not declared in interface
132
133 // NUMBER OF MATCHES
134 /**
135 * Returns the number of all pattern matches.
136 *
137 * @return the number of pattern matches found.
138 */
139 int countMatches();
140
141 /**
142 * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters.
143 *
144 * @param partialMatch
145 * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to
146 * a fixed value.
147 * @return the number of pattern matches found.
148 */
149 int countMatches(Match partialMatch);
150
151 // variant(s) with input binding as pattern-specific parameters: not declared in interface
152
153 // FOR EACH MATCH
154 /**
155 * Executes the given processor on each match of the pattern.
156 *
157 * @param processor
158 * the action that will process each pattern match.
159 * @since 2.0
160 */
161 void forEachMatch(Consumer<? super Match> processor);
162
163 /**
164 * Executes the given processor on each match of the pattern that conforms to the given fixed values of some
165 * parameters.
166 *
167 * @param partialMatch
168 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
169 * @param processor
170 * the action that will process each pattern match.
171 * @since 2.0
172 */
173 void forEachMatch(Match partialMatch, Consumer<? super Match> processor);
174
175 // variant(s) with input binding as pattern-specific parameters: not declared in interface
176
177 // FOR ONE ARBITRARY MATCH
178 /**
179 * Executes the given processor on an arbitrarily chosen match of the pattern. Neither determinism nor randomness of
180 * selection is guaranteed.
181 *
182 * @param processor
183 * the action that will process the selected match.
184 * @return true if the pattern has at least one match, false if the processor was not invoked
185 * @since 2.0
186 */
187 boolean forOneArbitraryMatch(Consumer<? super Match> processor);
188
189 /**
190 * Executes the given processor on an arbitrarily chosen match of the pattern that conforms to the given fixed
191 * values of some parameters. Neither determinism nor randomness of selection is guaranteed.
192 *
193 * @param partialMatch
194 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
195 * @param processor
196 * the action that will process the selected match.
197 * @return true if the pattern has at least one match with the given parameter values, false if the processor was
198 * not invoked
199 * @since 2.0
200 */
201 boolean forOneArbitraryMatch(Match partialMatch, Consumer<? super Match> processor);
202
203 // variant(s) with input binding as pattern-specific parameters: not declared in interface
204
205 /**
206 * Returns an empty, mutable Match for the matcher.
207 * Fields of the mutable match can be filled to create a partial match, usable as matcher input.
208 * This can be used to call the matcher with a partial match
209 * even if the specific class of the matcher or the match is unknown.
210 *
211 * @return the empty match
212 */
213 Match newEmptyMatch();
214
215 /**
216 * Returns a new (partial) Match object for the matcher.
217 * This can be used e.g. to call the matcher with a partial
218 * match.
219 *
220 * <p>The returned match will be immutable. Use {@link #newEmptyMatch()} to obtain a mutable match object.
221 *
222 * @param parameters
223 * the fixed value of pattern parameters, or null if not bound.
224 * @return the (partial) match object.
225 */
226 Match newMatch(Object... parameters);
227
228 /**
229 * Retrieve the set of values that occur in matches for the given parameterName.
230 *
231 * @param parameterName
232 * name of the parameter for which values are returned
233 * @return the Set of all values for the given parameter, null if the parameter with the given name does not exists,
234 * empty set if there are no matches
235 */
236 Set<Object> getAllValues(final String parameterName);
237
238 /**
239 * Retrieve the set of values that occur in matches for the given parameterName, that conforms to the given fixed
240 * values of some parameters.
241 *
242 * @param parameterName
243 * name of the parameter for which values are returned
244 * @param partialMatch
245 * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to
246 * a fixed value.
247 * @return the Set of all values for the given parameter, null if the parameter with the given name does not exists
248 * or if the parameter with the given name is set in partialMatch, empty set if there are no matches
249 */
250 Set<Object> getAllValues(final String parameterName, Match partialMatch);
251
252 /**
253 * Returns the engine that the matcher uses.
254 *
255 * @return the engine
256 */
257 ViatraQueryEngine getEngine();
258} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java
new file mode 100644
index 00000000..da8bf87e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api;
10
11
12
13/**
14 * Listener interface for model changes affecting different levels of the VIATRA Query architecture.
15 *
16 * @author Abel Hegedus
17 *
18 */
19public interface ViatraQueryModelUpdateListener {
20
21 /**
22 * Possible notification levels for changes
23 *
24 * @author Abel Hegedus
25 *
26 */
27 enum ChangeLevel {
28 NO_CHANGE, MODEL, INDEX, MATCHSET;
29
30 public ChangeLevel changeOccured(ChangeLevel occuredLevel) {
31 if(this.compareTo(occuredLevel) < 0) {
32 return occuredLevel;
33 } else {
34 return this;
35 }
36 }
37 }
38 /**
39 * Called after each change with also sending the level of change.
40 * Only called if the change level is at least at the level returned by getLevel().
41 *
42 * @param changeLevel
43 */
44 void notifyChanged(ChangeLevel changeLevel);
45
46 /**
47 * This may be queried only ONCE (!!!) at the registration of the listener.
48 *
49 * NOTE: this allows us to only create engine level change providers if there is someone who needs it.
50 *
51 * @return the change level where you want notifications
52 */
53 ChangeLevel getLevel();
54
55}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java
new file mode 100644
index 00000000..2e2a823b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Mark Czotter, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.impl;
10
11import java.util.HashSet;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.api.IQuerySpecification;
15
16/**
17 * @author Mark Czotter
18 *
19 */
20public abstract class BaseGeneratedPatternGroup extends BaseQueryGroup {
21
22 @Override
23 public Set<IQuerySpecification<?>> getSpecifications() {
24 return querySpecifications;
25 }
26
27 /**
28 * Returns {@link IQuerySpecification} objects for handling them as a group. To be filled by constructors of subclasses.
29 */
30 protected Set<IQuerySpecification<?>> querySpecifications = new HashSet<IQuerySpecification<?>>();
31}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java
new file mode 100644
index 00000000..ad79aafd
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java
@@ -0,0 +1,350 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api.impl;
11
12import java.util.Collection;
13import java.util.List;
14import java.util.Optional;
15import java.util.Set;
16import java.util.function.Consumer;
17import java.util.stream.Collectors;
18import java.util.stream.Stream;
19
20import tools.refinery.viatra.runtime.api.IPatternMatch;
21import tools.refinery.viatra.runtime.api.IQuerySpecification;
22import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
23import tools.refinery.viatra.runtime.api.ViatraQueryMatcher;
24import tools.refinery.viatra.runtime.internal.apiimpl.QueryResultWrapper;
25import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
26import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
27import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
28import tools.refinery.viatra.runtime.matchers.util.Preconditions;
29
30/**
31 * Base implementation of ViatraQueryMatcher.
32 *
33 * @author Bergmann Gábor
34 *
35 * @param <Match>
36 */
37public abstract class BaseMatcher<Match extends IPatternMatch> extends QueryResultWrapper implements ViatraQueryMatcher<Match> {
38
39 // FIELDS AND CONSTRUCTOR
40
41 protected ViatraQueryEngine engine;
42 protected IQuerySpecification<? extends BaseMatcher<Match>> querySpecification;
43 private IMatcherCapability capabilities;
44
45 /**
46 * @since 1.4
47 */
48 public BaseMatcher(IQuerySpecification<? extends BaseMatcher<Match>> querySpecification) {
49 this.querySpecification = querySpecification;
50 this.querySpecification.getInternalQueryRepresentation().ensureInitialized();
51 }
52
53 /**
54 * @since 1.4
55 */
56 @Override
57 protected
58 void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, IMatcherCapability capabilities){
59 this.backend = resultProvider;
60 this.engine = engine;
61 this.capabilities = capabilities;
62 }
63
64 // ARRAY-BASED INTERFACE
65
66 /** Converts the array representation of a pattern match to an immutable Match object. */
67 protected abstract Match arrayToMatch(Object[] parameters);
68 /** Converts the array representation of a pattern match to a mutable Match object. */
69 protected abstract Match arrayToMatchMutable(Object[] parameters);
70
71 /** Converts the Match object of a pattern match to the array representation. */
72 protected Object[] matchToArray(Match partialMatch) {
73 return partialMatch.toArray();
74 }
75 // TODO make me public for performance reasons
76 protected abstract Match tupleToMatch(Tuple t);
77
78 private Object[] fEmptyArray;
79
80 protected Object[] emptyArray() {
81 if (fEmptyArray == null)
82 fEmptyArray = new Object[getSpecification().getParameterNames().size()];
83 return fEmptyArray;
84 }
85
86 // REFLECTION
87
88 @Override
89 public Integer getPositionOfParameter(String parameterName) {
90 return getSpecification().getPositionOfParameter(parameterName);
91 }
92
93 @Override
94 public List<String> getParameterNames() {
95 return getSpecification().getParameterNames();
96 }
97
98 // BASE IMPLEMENTATION
99
100 @Override
101 public Collection<Match> getAllMatches() {
102 return rawStreamAllMatches(emptyArray()).collect(Collectors.toSet());
103 }
104
105 @Override
106 public Stream<Match> streamAllMatches() {
107 return rawStreamAllMatches(emptyArray());
108 }
109
110 /**
111 * Returns a stream of all matches of the pattern that conform to the given fixed values of some parameters.
112 *
113 * @param parameters
114 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
115 * @pre size of input array must be equal to the number of parameters.
116 * @return matches represented as a Match object.
117 * @since 2.0
118 */
119 protected Stream<Match> rawStreamAllMatches(Object[] parameters) {
120 // clones the tuples into a match object to protect the Tuples from modifications outside of the ReteMatcher
121 return backend.getAllMatches(parameters).map(this::tupleToMatch);
122 }
123
124 @Override
125 public Collection<Match> getAllMatches(Match partialMatch) {
126 return rawStreamAllMatches(partialMatch.toArray()).collect(Collectors.toSet());
127 }
128
129 @Override
130 public Stream<Match> streamAllMatches(Match partialMatch) {
131 return rawStreamAllMatches(partialMatch.toArray());
132 }
133
134 // with input binding as pattern-specific parameters: not declared in interface
135
136 @Override
137 public Optional<Match> getOneArbitraryMatch() {
138 return rawGetOneArbitraryMatch(emptyArray());
139 }
140
141 /**
142 * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters.
143 * Neither determinism nor randomness of selection is guaranteed.
144 *
145 * @param parameters
146 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
147 * @pre size of input array must be equal to the number of parameters.
148 * @return a match represented as a Match object, or null if no match is found.
149 * @since 2.0
150 */
151 protected Optional<Match> rawGetOneArbitraryMatch(Object[] parameters) {
152 return backend.getOneArbitraryMatch(parameters).map(this::tupleToMatch);
153 }
154
155 @Override
156 public Optional<Match> getOneArbitraryMatch(Match partialMatch) {
157 return rawGetOneArbitraryMatch(partialMatch.toArray());
158 }
159
160 // with input binding as pattern-specific parameters: not declared in interface
161
162 /**
163 * Indicates whether the given combination of specified pattern parameters constitute a valid pattern match, under
164 * any possible substitution of the unspecified parameters.
165 *
166 * @param parameters
167 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
168 * @return true if the input is a valid (partial) match of the pattern.
169 */
170 protected boolean rawHasMatch(Object[] parameters) {
171 return backend.hasMatch(parameters);
172 }
173
174 @Override
175 public boolean hasMatch() {
176 return rawHasMatch(emptyArray());
177 }
178
179 @Override
180 public boolean hasMatch(Match partialMatch) {
181 return rawHasMatch(partialMatch.toArray());
182 }
183
184 // with input binding as pattern-specific parameters: not declared in interface
185
186 @Override
187 public int countMatches() {
188 return rawCountMatches(emptyArray());
189 }
190
191 /**
192 * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters.
193 *
194 * @param parameters
195 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
196 * @pre size of input array must be equal to the number of parameters.
197 * @return the number of pattern matches found.
198 */
199 protected int rawCountMatches(Object[] parameters) {
200 return backend.countMatches(parameters);
201 }
202
203 @Override
204 public int countMatches(Match partialMatch) {
205 return rawCountMatches(partialMatch.toArray());
206 }
207
208 // with input binding as pattern-specific parameters: not declared in interface
209
210 /**
211 * Executes the given processor on each match of the pattern that conforms to the given fixed values of some
212 * parameters.
213 *
214 * @param parameters
215 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
216 * @pre size of input array must be equal to the number of parameters.
217 * @param action
218 * the action that will process each pattern match.
219 * @since 2.0
220 */
221 protected void rawForEachMatch(Object[] parameters, Consumer<? super Match> processor) {
222 backend.getAllMatches(parameters).map(this::tupleToMatch).forEach(processor);
223 }
224
225 @Override
226 public void forEachMatch(Consumer<? super Match> processor) {
227 rawForEachMatch(emptyArray(), processor);
228 }
229
230 @Override
231 public void forEachMatch(Match match, Consumer<? super Match> processor) {
232 rawForEachMatch(match.toArray(), processor);
233 }
234
235 // with input binding as pattern-specific parameters: not declared in interface
236
237 @Override
238 public boolean forOneArbitraryMatch(Consumer<? super Match> processor) {
239 return rawForOneArbitraryMatch(emptyArray(), processor);
240 }
241
242 @Override
243 public boolean forOneArbitraryMatch(Match partialMatch, Consumer<? super Match> processor) {
244 return rawForOneArbitraryMatch(partialMatch.toArray(), processor);
245 }
246
247 /**
248 * Executes the given processor on an arbitrarily chosen match of the pattern that conforms to the given fixed
249 * values of some parameters. Neither determinism nor randomness of selection is guaranteed.
250 *
251 * @param parameters
252 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
253 * @pre size of input array must be equal to the number of parameters.
254 * @param processor
255 * the action that will process the selected match.
256 * @return true if the pattern has at least one match with the given parameter values, false if the processor was
257 * not invoked
258 * @since 2.0
259 */
260 protected boolean rawForOneArbitraryMatch(Object[] parameters, Consumer<? super Match> processor) {
261 return backend.getOneArbitraryMatch(parameters).map(this::tupleToMatch).map(m -> {
262 processor.accept(m);
263 return true;
264 }).orElse(false);
265 }
266
267 // with input binding as pattern-specific parameters: not declared in interface
268
269
270 @Override
271 public Match newEmptyMatch() {
272 return arrayToMatchMutable(new Object[getParameterNames().size()]);
273 }
274
275 @Override
276 public Match newMatch(Object... parameters) {
277 return arrayToMatch(parameters);
278 }
279
280 @Override
281 public Set<Object> getAllValues(final String parameterName) {
282 return rawStreamAllValues(getPositionOfParameter(parameterName), emptyArray()).collect(Collectors.toSet());
283 }
284
285 @Override
286 public Set<Object> getAllValues(final String parameterName, Match partialMatch) {
287 return rawStreamAllValues(getPositionOfParameter(parameterName), partialMatch.toArray()).collect(Collectors.toSet());
288 }
289
290 /**
291 * Retrieve a stream of values that occur in matches for the given parameterName, that conforms to the given fixed
292 * values of some parameters.
293 *
294 * @param position
295 * position of the parameter for which values are returned
296 * @param parameters
297 * a parameter array corresponding to a partial match of the pattern where each non-null field binds the
298 * corresponding pattern parameter to a fixed value.
299 * @return the stream of all values in the given position
300 * @throws IllegalArgumentException
301 * if length of parameters array does not equal to number of parameters
302 * @throws IndexOutOfBoundsException
303 * if position is not appropriate for the current parameter size
304 * @since 2.0
305 */
306 protected Stream<Object> rawStreamAllValues(final int position, Object[] parameters) {
307 Preconditions.checkElementIndex(position, getParameterNames().size());
308 Preconditions.checkArgument(parameters.length == getParameterNames().size());
309 return rawStreamAllMatches(parameters).map(match -> match.get(position));
310 }
311
312 /**
313 * Uses an existing set to accumulate all values of the parameter with the given name. Since it is a protected
314 * method, no error checking or input validation is performed!
315 *
316 * @param position
317 * position of the parameter for which values are returned
318 * @param parameters
319 * a parameter array corresponding to a partial match of the pattern where each non-null field binds the
320 * corresponding pattern parameter to a fixed value.
321 * @param accumulator
322 * the existing set to fill with the values
323 */
324 @SuppressWarnings("unchecked")
325 protected <T> void rawAccumulateAllValues(final int position, Object[] parameters, final Set<T> accumulator) {
326 rawForEachMatch(parameters, match -> accumulator.add((T) match.get(position)));
327 }
328
329 @Override
330 public ViatraQueryEngine getEngine() {
331 return engine;
332 }
333
334 @Override
335 public IQuerySpecification<? extends BaseMatcher<Match>> getSpecification() {
336 return querySpecification;
337 }
338
339 @Override
340 public String getPatternName() {
341 return querySpecification.getFullyQualifiedName();
342 }
343
344 /**
345 * @since 1.4
346 */
347 public IMatcherCapability getCapabilities() {
348 return capabilities;
349 }
350}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java
new file mode 100644
index 00000000..182bb466
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java
@@ -0,0 +1,91 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api.impl;
11
12import tools.refinery.viatra.runtime.api.IPatternMatch;
13
14import java.util.Arrays;
15import java.util.Collections;
16import java.util.List;
17
18/**
19 * Base implementation of IPatternMatch.
20 *
21 * @author Bergmann Gábor
22 *
23 */
24public abstract class BasePatternMatch implements IPatternMatch {
25
26 @SafeVarargs
27 protected static <T> List<T> makeImmutableList(T... elements) {
28 return Collections.unmodifiableList(Arrays.asList(elements));
29 }
30
31 public static String prettyPrintValue(Object o) {
32 if (o == null) {
33 return "(null)";
34 }
35 return o.toString();
36 }
37
38 // TODO performance can be improved here somewhat
39
40 @Override
41 public Object get(int position) {
42 if (position >= 0 && position < parameterNames().size())
43 return get(parameterNames().get(position));
44 else
45 return null;
46 }
47
48 @Override
49 public boolean set(int position, Object newValue) {
50 if (!isMutable()) throw new UnsupportedOperationException();
51 if (position >= 0 && position < parameterNames().size()) {
52 return set(parameterNames().get(position), newValue);
53 } else {
54 return false;
55 }
56 }
57
58 @Override
59 public String toString() {
60 return "Match<" + patternName() + ">{" + prettyPrint() + "}";
61 }
62
63 @Override
64 public boolean isCompatibleWith(IPatternMatch other) {
65 if(other == null) {
66 return true;
67 }
68 // we assume that the pattern is set for this match!
69 if (!specification().equals(other.specification())) {
70 return false;
71 }
72 for (int i = 0; i < parameterNames().size(); i++) {
73 Object value = get(i);
74 Object otherValue = other.get(i);
75 if(value != null && otherValue != null && !value.equals(otherValue)) {
76 return false;
77 }
78 }
79 return true;
80 }
81
82 @Override
83 public String patternName() {
84 return specification().getFullyQualifiedName();
85 }
86
87 @Override
88 public List<String> parameterNames() {
89 return specification().getParameterNames();
90 }
91}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java
new file mode 100644
index 00000000..b92727e7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Mark Czotter, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.impl;
10
11import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine;
12import tools.refinery.viatra.runtime.api.IQueryGroup;
13import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
14
15/**
16 * Base implementation of {@link IQueryGroup}.
17 *
18 * @author Mark Czotter
19 *
20 */
21public abstract class BaseQueryGroup implements IQueryGroup {
22
23 @Override
24 public void prepare(ViatraQueryEngine engine) {
25 prepare(AdvancedViatraQueryEngine.from(engine));
26 }
27
28 protected void prepare(AdvancedViatraQueryEngine engine) {
29 engine.prepareGroup(this, null /* default options */);
30 }
31
32
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java
new file mode 100644
index 00000000..bee4b93d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java
@@ -0,0 +1,147 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.api.impl;
11
12import java.util.List;
13import java.util.Optional;
14import java.util.stream.Collectors;
15
16import tools.refinery.viatra.runtime.api.IPatternMatch;
17import tools.refinery.viatra.runtime.api.IQuerySpecification;
18import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
19import tools.refinery.viatra.runtime.api.ViatraQueryMatcher;
20import tools.refinery.viatra.runtime.exception.ViatraQueryException;
21import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
23import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
24import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException;
25import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
26import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility;
27
28/**
29 * Base implementation of IQuerySpecification.
30 *
31 * @author Gabor Bergmann
32 *
33 */
34public abstract class BaseQuerySpecification<Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> implements
35 IQuerySpecification<Matcher> {
36
37 /**
38 * @since 1.6
39 */
40 protected static ViatraQueryException processInitializerError(ExceptionInInitializerError err) {
41 Throwable cause1 = err.getCause();
42 if (cause1 instanceof RuntimeException) {
43 Throwable cause2 = ((RuntimeException) cause1).getCause();
44 if (cause2 instanceof ViatraQueryException) {
45 return (ViatraQueryException) cause2;
46 } else if (cause2 instanceof QueryInitializationException) {
47 return new ViatraQueryException((QueryInitializationException) cause2);
48 }
49 }
50 throw err;
51 }
52 protected final PQuery wrappedPQuery;
53
54 protected abstract Matcher instantiate(ViatraQueryEngine engine);
55
56 /**
57 * For backward compatibility of code generated with previous versions of viatra query, this method has a default
58 * implementation returning null, indicating that a matcher can only be created using the old method, which ignores
59 * the hints provided by the user.
60 *
61 * @since 1.4
62 */
63 @Override
64 public Matcher instantiate() {
65 return null;
66 }
67
68
69 /**
70 * Instantiates query specification for the given internal query representation.
71 */
72 public BaseQuerySpecification(PQuery wrappedPQuery) {
73 super();
74 this.wrappedPQuery = wrappedPQuery;
75 wrappedPQuery.publishedAs().add(this);
76 }
77
78
79 @Override
80 public PQuery getInternalQueryRepresentation() {
81 return wrappedPQuery;
82 }
83
84 @Override
85 public Matcher getMatcher(ViatraQueryEngine engine) {
86 ensureInitializedInternal();
87 if (wrappedPQuery.getStatus() == PQueryStatus.ERROR) {
88 String errorMessages = wrappedPQuery.getPProblems().stream()
89 .map(input -> (input == null) ? "" : input.getShortMessage()).collect(Collectors.joining("\n"));
90 throw new ViatraQueryException(String.format("Erroneous query specification: %s %n %s", getFullyQualifiedName(), errorMessages),
91 "Cannot initialize matchers on erroneous query specifications.");
92 } else if (!engine.getScope().isCompatibleWithQueryScope(this.getPreferredScopeClass())) {
93 throw new ViatraQueryException(
94 String.format(
95 "Scope class incompatibility: the query %s is formulated over query scopes of class %s, "
96 + " thus the query engine formulated over scope %s of class %s cannot evaluate it.",
97 this.getFullyQualifiedName(), this.getPreferredScopeClass().getCanonicalName(),
98 engine.getScope(), engine.getScope().getClass().getCanonicalName()),
99 "Incompatible scope classes of engine and query.");
100 }
101 return instantiate(engine);
102 }
103
104 protected void ensureInitializedInternal() {
105 wrappedPQuery.ensureInitialized();
106 }
107
108 // // DELEGATIONS
109
110 @Override
111 public List<PAnnotation> getAllAnnotations() {
112 return wrappedPQuery.getAllAnnotations();
113 }
114 @Override
115 public List<PAnnotation> getAnnotationsByName(String annotationName) {
116 return wrappedPQuery.getAnnotationsByName(annotationName);
117 }
118 @Override
119 public Optional<PAnnotation> getFirstAnnotationByName(String annotationName) {
120 return wrappedPQuery.getFirstAnnotationByName(annotationName);
121 }
122 @Override
123 public String getFullyQualifiedName() {
124 return wrappedPQuery.getFullyQualifiedName();
125 }
126 @Override
127 public List<String> getParameterNames() {
128 return wrappedPQuery.getParameterNames();
129 }
130 @Override
131 public List<PParameter> getParameters() {
132 return wrappedPQuery.getParameters();
133 }
134 @Override
135 public Integer getPositionOfParameter(String parameterName) {
136 return wrappedPQuery.getPositionOfParameter(parameterName);
137 }
138
139 /**
140 * @since 2.0
141 */
142 @Override
143 public PVisibility getVisibility() {
144 return wrappedPQuery.getVisibility();
145 }
146
147}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java
new file mode 100644
index 00000000..1795a8ef
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java
@@ -0,0 +1,91 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11import java.lang.reflect.InvocationTargetException;
12import java.util.concurrent.Callable;
13
14/**
15 * Represents the index maintained on the model.
16 * @author Bergmann Gabor
17 * @since 0.9
18 *
19 */
20public interface IBaseIndex {
21 // TODO lightweightObserver?
22 // TODO ViatraBaseIndexChangeListener?
23
24 /**
25 * The given callback will be executed, and all model traversals and index registrations will be delayed until the
26 * execution is done. If there are any outstanding feature, class or datatype registrations, a single coalesced model
27 * traversal will initialize the caches and deliver the notifications.
28 *
29 * @param callable
30 */
31 public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException;
32
33 /**
34 * Adds a coarse-grained listener that will be invoked after the NavigationHelper index or the underlying model is changed. Can be used
35 * e.g. to check model contents. Not intended for general use.
36 *
37 * <p/> See {@link #removeBaseIndexChangeListener(ViatraBaseIndexChangeListener)}
38 * @param listener
39 */
40 public void addBaseIndexChangeListener(ViatraBaseIndexChangeListener listener);
41
42 /**
43 * Removes a registered listener.
44 *
45 * <p/> See {@link #addBaseIndexChangeListener(ViatraBaseIndexChangeListener)}
46 *
47 * @param listener
48 */
49 public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener);
50
51 /**
52 * Updates the value of indexed derived features that are not well-behaving.
53 */
54 void resampleDerivedFeatures();
55
56 /**
57 * Adds a listener for internal errors in the index. A listener can only be added once.
58 * @param listener
59 * @returns true if the listener was not already added
60 * @since 0.8.0
61 */
62 boolean addIndexingErrorListener(IIndexingErrorListener listener);
63 /**
64 * Removes a listener for internal errors in the index
65 * @param listener
66 * @returns true if the listener was successfully removed (e.g. it did exist)
67 * @since 0.8.0
68 */
69 boolean removeIndexingErrorListener(IIndexingErrorListener listener);
70
71 /**
72 * Register a lightweight observer that is notified if any edge starting at the given Object changes.
73 *
74 * @param observer the listener instance
75 * @param observedObject the observed instance object
76 * @return false if no observer can be registered for the given instance (e.g. it is a primitive),
77 * or observer was already registered (call has no effect)
78 */
79 public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject);
80
81 /**
82 * Unregisters a lightweight observer for the given Object.
83 *
84 * @param observer the listener instance
85 * @param observedObject the observed instance object
86 * @return false if no observer can be registered for the given instance (e.g. it is a primitive),
87 * or no observer was registered previously (call has no effect)
88 */
89 public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject);
90
91}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java
new file mode 100644
index 00000000..55060853
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
13
14/**
15 * The context of the engine is instantiated by the scope,
16 * and provides information and services regarding the model the towards the engine.
17 *
18 * @author Bergmann Gabor
19 *
20 */
21public interface IEngineContext {
22
23 /**
24 * Returns the base index.
25 * @throws ViatraQueryRuntimeException if the base index cannot be accessed
26 */
27 IBaseIndex getBaseIndex();
28
29 /**
30 * Disposes this context object. Resources in the index may now be freed up.
31 * No more methods should be called after this one.
32 *
33 * @throws IllegalStateException if there are any active listeners to the underlying index
34 */
35 void dispose();
36
37 /**
38 * Provides instance model information for pattern matching.
39 *
40 * <p> Implementors note: must be reentrant.
41 * If called while index loading is already in progress, must return the single runtime context instance that will eventually index the model.
42 * When the runtime query context is invoked in such a case, incomplete indexes are tolerable, but change notifications must be correctly provided as loading commences.
43 *
44 * @return a runtime context for pattern matching
45 * @since 1.2
46 * @throws ViatraQueryRuntimeException if the runtime context cannot be initialized
47 */
48 public IQueryRuntimeContext getQueryRuntimeContext();
49} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java
new file mode 100644
index 00000000..f61a5edb
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11/**
12 *
13 * This interface contains callbacks for various internal errors from the {@link NavigationHelper base index}.
14 *
15 * @author Zoltan Ujhelyi
16 * @since 0.9
17 *
18 */
19public interface IIndexingErrorListener {
20
21 void error(String description, Throwable t);
22 void fatal(String description, Throwable t);
23}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java
new file mode 100644
index 00000000..8ef29cbe
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11
12/**
13 * Listener interface for lightweight observation of changes in edges leaving from given source instance elements.
14 * @author Bergmann Gabor
15 * @since 0.9
16 *
17 */
18public interface IInstanceObserver {
19 void notifyBinaryChanged(Object sourceElement, Object edgeType);
20 void notifyTernaryChanged(Object sourceElement, Object edgeType);
21}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java
new file mode 100644
index 00000000..5456b9ea
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11import tools.refinery.viatra.runtime.api.IQuerySpecification;
12import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
13import tools.refinery.viatra.runtime.internal.apiimpl.EngineContextFactory;
14
15/**
16 * Defines a scope for a VIATRA Query engine, which determines the set of model elements that query evaluation operates on.
17 *
18 * @author Bergmann Gabor
19 *
20 */
21public abstract class QueryScope extends EngineContextFactory {
22
23 /**
24 * Determines whether a query engine initialized on this scope can evaluate queries formulated against the given scope type.
25 * <p> Every query scope class is compatible with a query engine initialized on a scope of the same class or a subclass.
26 * @param queryScopeClass the scope class returned by invoking {@link IQuerySpecification#getPreferredScopeClass()} on a query specification
27 * @return true if an {@link ViatraQueryEngine} initialized on this scope can consume an {@link IQuerySpecification}
28 */
29 public boolean isCompatibleWithQueryScope(Class<? extends QueryScope> queryScopeClass) {
30 return queryScopeClass.isAssignableFrom(this.getClass());
31 }
32
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java
new file mode 100644
index 00000000..b746e637
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.api.scope;
10
11/**
12 * Listener interface for change notifications from the VIATRA Base index.
13 *
14 * @author Abel Hegedus
15 * @since 0.9
16 *
17 */
18public interface ViatraBaseIndexChangeListener {
19
20 /**
21 * NOTE: it is possible that this method is called only ONCE! Consider returning a constant value that is set in the constructor.
22 *
23 * @return true, if the listener should be notified only after index changes, false if notification is needed after each model change
24 */
25 public boolean onlyOnIndexChange();
26
27 /**
28 * Called after a model change is handled by the VIATRA Base index and if <code>indexChanged == onlyOnIndexChange()</code>.
29 *
30 * @param indexChanged true, if the model change also affected the contents of the base index
31 */
32 public void notifyChanged(boolean indexChanged);
33
34}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java
new file mode 100644
index 00000000..fec547a6
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java
@@ -0,0 +1,71 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Akos Horvath 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.exception;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
13import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException;
14
15/**
16 * A general VIATRA Query-related problem during the operation of the VIATRA Query
17 * engine, or the loading, manipulation and evaluation of queries.
18 *
19 * @author Bergmann Gabor
20 * @since 0.9
21 *
22 */
23public class ViatraQueryException extends ViatraQueryRuntimeException {
24
25 private static final long serialVersionUID = -74252748358355750L;
26
27 public static final String PARAM_NOT_SUITABLE_WITH_NO = "The type of the parameters are not suitable for the operation. Parameter number: ";
28 public static final String CONVERSION_FAILED = "Could not convert the term to the designated type";
29 public static final String CONVERT_NULL_PARAMETER = "Could not convert null to the designated type";
30 public static final String RELATIONAL_PARAM_UNSUITABLE = "The parameters are not acceptable by the operation";
31 /**
32 * @since 0.9
33 */
34 public static final String PROCESSING_PROBLEM = "The following error occurred during the processing of a query (e.g. for the preparation of a VIATRA pattern matcher)";
35 /**
36 * @since 0.9
37 */
38 public static final String QUERY_INIT_PROBLEM = "The following error occurred during the initialization of a VIATRA query specification";
39 public static final String GETNAME_FAILED = "Could not get 'name' attribute of the result";
40
41 public static final String INVALID_EMFROOT = "Incremental EMF query engine can only be attached on the contents of an EMF EObject, Resource, ResourceSet or multiple ResourceSets. Received instead: ";
42 public static final String INVALID_EMFROOT_SHORT = "Invalid EMF model root";
43 // public static final String EMF_MODEL_PROCESSING_ERROR = "Error while processing the EMF model";
44
45 private final String shortMessage;
46
47 public ViatraQueryException(String s, String shortMessage) {
48 super(s);
49 this.shortMessage = shortMessage;
50 }
51
52 public ViatraQueryException(QueryProcessingException e) {
53 super(PROCESSING_PROBLEM + ": " + e.getMessage(), e);
54 this.shortMessage = e.getShortMessage();
55 }
56
57 public ViatraQueryException(QueryInitializationException e) {
58 super(QUERY_INIT_PROBLEM + ": " + e.getMessage(), e);
59 this.shortMessage = e.getShortMessage();
60 }
61
62 public ViatraQueryException(String s, String shortMessage, Throwable e) {
63 super(s + ": " + e.getMessage(), e);
64 this.shortMessage = shortMessage;
65 }
66
67 public String getShortMessage() {
68 return shortMessage;
69 }
70
71}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java
new file mode 100644
index 00000000..bed07ebf
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.apiimpl;
10
11import org.apache.log4j.Logger;
12import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
13import tools.refinery.viatra.runtime.api.scope.IEngineContext;
14import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener;
15
16/**
17 * Internal interface for a Scope to reveal model contents to the engine.
18 *
19 * @author Bergmann Gabor
20 *
21 */
22public abstract class EngineContextFactory {
23 protected abstract IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, Logger logger);
24}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java
new file mode 100644
index 00000000..47f3268d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.apiimpl;
10
11import tools.refinery.viatra.runtime.api.ViatraQueryEngine;
12import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
13import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
14
15/**
16 * Internal class for wrapping a query result providing backend. It's only supported usage is by the
17 * {@link ViatraQueryEngineImpl} class.
18 * </p>
19 *
20 * <strong>Important note</strong>: this class must not introduce any public method, as it will be visible through
21 * BaseMatcher as an API, although this class is not an API itself.
22 *
23 * @author Bergmann Gabor
24 *
25 */
26public abstract class QueryResultWrapper {
27
28 protected IQueryResultProvider backend;
29
30 protected abstract void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, IMatcherCapability capabilities);
31
32}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java
new file mode 100644
index 00000000..47a51629
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java
@@ -0,0 +1,693 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10
11package tools.refinery.viatra.runtime.internal.apiimpl;
12
13import org.apache.log4j.Logger;
14import tools.refinery.viatra.runtime.api.*;
15import tools.refinery.viatra.runtime.api.impl.BaseMatcher;
16import tools.refinery.viatra.runtime.api.scope.IBaseIndex;
17import tools.refinery.viatra.runtime.api.scope.IEngineContext;
18import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener;
19import tools.refinery.viatra.runtime.api.scope.QueryScope;
20import tools.refinery.viatra.runtime.exception.ViatraQueryException;
21import tools.refinery.viatra.runtime.internal.engine.LifecycleProvider;
22import tools.refinery.viatra.runtime.internal.engine.ModelUpdateProvider;
23import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
24import tools.refinery.viatra.runtime.matchers.backend.*;
25import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
26import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext;
27import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess;
28import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
29import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
30import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
31import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries;
32import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
33import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
34import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
35import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
36import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
37import tools.refinery.viatra.runtime.matchers.util.Preconditions;
38import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil;
39
40import java.lang.ref.WeakReference;
41import java.lang.reflect.InvocationTargetException;
42import java.util.*;
43import java.util.concurrent.Callable;
44import java.util.stream.Collectors;
45
46import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument;
47
48/**
49 * A VIATRA Query engine back-end (implementation)
50 *
51 * @author Bergmann Gábor
52 */
53public final class ViatraQueryEngineImpl extends AdvancedViatraQueryEngine
54 implements IQueryBackendHintProvider, IQueryCacheContext, IQueryResultProviderAccess {
55
56 /**
57 *
58 */
59 private static final String ERROR_ACCESSING_BACKEND = "Error while accessing query evaluator backend";
60 /**
61 *
62 */
63 private static final String QUERY_ON_DISPOSED_ENGINE_MESSAGE = "Cannot evaluate query on disposed engine!";
64 /**
65 * The engine manager responsible for this engine. Null if this engine is unmanaged.
66 */
67 private final ViatraQueryEngineManager manager;
68 /**
69 * The model to which the engine is attached.
70 */
71 private final QueryScope scope;
72
73 /**
74 * The context of the engine, provided by the scope.
75 */
76 private IEngineContext engineContext;
77
78 /**
79 * Initialized matchers for each query
80 */
81 private final IMultiLookup<IQuerySpecification<? extends ViatraQueryMatcher<?>>, ViatraQueryMatcher<?>> matchers =
82 CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
83
84 /**
85 * The RETE and other pattern matcher implementations of the VIATRA Query Engine.
86 */
87 private volatile Map<IQueryBackendFactory, IQueryBackend> queryBackends = new HashMap<>();
88
89 /**
90 * The current engine default hints
91 */
92 private final ViatraQueryEngineOptions engineOptions;
93
94 /**
95 * Common query analysis provided to backends
96 */
97 private QueryAnalyzer queryAnalyzer;
98
99 /**
100 * true if message delivery is currently delayed, false otherwise
101 */
102 private boolean delayMessageDelivery = true;
103
104 private final LifecycleProvider lifecycleProvider;
105 private final ModelUpdateProvider modelUpdateProvider;
106 private Logger logger;
107 private boolean disposed = false;
108
109 /**
110 * @param manager
111 * null if unmanaged
112 * @param scope
113 * @param engineDefaultHint
114 * @since 1.4
115 */
116 public ViatraQueryEngineImpl(ViatraQueryEngineManager manager, QueryScope scope,
117 ViatraQueryEngineOptions engineOptions) {
118 super();
119 this.manager = manager;
120 this.scope = scope;
121 this.lifecycleProvider = new LifecycleProvider(this, getLogger());
122 this.modelUpdateProvider = new ModelUpdateProvider(this, getLogger());
123 this.engineContext = scope.createEngineContext(this, taintListener, getLogger());
124
125 if (engineOptions != null) {
126 this.engineOptions = engineOptions;
127 } else {
128 this.engineOptions = ViatraQueryEngineOptions.getDefault();
129 }
130
131 }
132
133 /**
134 * @param manager
135 * null if unmanaged
136 * @param scope
137 * @param engineDefaultHint
138 */
139 public ViatraQueryEngineImpl(ViatraQueryEngineManager manager, QueryScope scope) {
140 this(manager, scope, ViatraQueryEngineOptions.getDefault());
141 }
142
143 @Override
144 public boolean isUpdatePropagationDelayed() {
145 return this.delayMessageDelivery;
146 }
147
148 @Override
149 public <V> V delayUpdatePropagation(Callable<V> callable) throws InvocationTargetException {
150 if (!delayMessageDelivery) {
151 throw new IllegalStateException("Trying to delay propagation while changes are being flushed");
152 }
153 try {
154 return callable.call();
155 } catch (Exception e) {
156 throw new InvocationTargetException(e);
157 }
158 }
159
160 @Override
161 public void flushChanges() {
162 if (!delayMessageDelivery) {
163 throw new IllegalStateException("Trying to flush changes while changes are already being flushed");
164 }
165 delayMessageDelivery = false;
166 try {
167 for (IQueryBackend backend : this.queryBackends.values()) {
168 backend.flushUpdates();
169 }
170 } finally {
171 delayMessageDelivery = true;
172 }
173 }
174
175 @Override
176 public Set<? extends ViatraQueryMatcher<? extends IPatternMatch>> getCurrentMatchers() {
177 return matchers.distinctValuesStream().collect(Collectors.toSet());
178 }
179
180 @Override
181 public <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getMatcher(
182 IQuerySpecification<Matcher> querySpecification) {
183 return getMatcher(querySpecification, null);
184 }
185
186 @Override
187 public <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getMatcher(
188 IQuerySpecification<Matcher> querySpecification, QueryEvaluationHint optionalEvaluationHints) {
189 IMatcherCapability capability = getRequestedCapability(querySpecification, optionalEvaluationHints);
190 Matcher matcher = doGetExistingMatcher(querySpecification, capability);
191 if (matcher != null) {
192 return matcher;
193 }
194 matcher = querySpecification.instantiate();
195
196 BaseMatcher<?> baseMatcher = (BaseMatcher<?>) matcher;
197 ((QueryResultWrapper) baseMatcher).setBackend(this,
198 getResultProvider(querySpecification, optionalEvaluationHints), capability);
199 internalRegisterMatcher(querySpecification, baseMatcher);
200 return matcher;
201 }
202
203 @Override
204 public <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getExistingMatcher(
205 IQuerySpecification<Matcher> querySpecification) {
206 return getExistingMatcher(querySpecification, null);
207 }
208
209 @Override
210 public <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher getExistingMatcher(
211 IQuerySpecification<Matcher> querySpecification, QueryEvaluationHint optionalOverrideHints) {
212 return doGetExistingMatcher(querySpecification, getRequestedCapability(querySpecification, optionalOverrideHints));
213 }
214
215 @SuppressWarnings("unchecked")
216 private <Matcher extends ViatraQueryMatcher<? extends IPatternMatch>> Matcher doGetExistingMatcher(
217 IQuerySpecification<Matcher> querySpecification, IMatcherCapability requestedCapability) {
218 for (ViatraQueryMatcher<?> matcher : matchers.lookupOrEmpty(querySpecification)) {
219 BaseMatcher<?> baseMatcher = (BaseMatcher<?>) matcher;
220 if (baseMatcher.getCapabilities().canBeSubstitute(requestedCapability))
221 return (Matcher) matcher;
222 }
223 return null;
224 }
225
226 @Override
227 public ViatraQueryMatcher<? extends IPatternMatch> getMatcher(String patternFQN) {
228 throw new UnsupportedOperationException("Query specification registry is not available");
229 }
230
231 @Override
232 public IBaseIndex getBaseIndex() {
233 return engineContext.getBaseIndex();
234 }
235
236 public final Logger getLogger() {
237 if (logger == null) {
238 final int hash = System.identityHashCode(this);
239 logger = Logger.getLogger(ViatraQueryLoggingUtil.getLogger(ViatraQueryEngine.class).getName() + "." + hash);
240 if (logger == null)
241 throw new AssertionError(
242 "Configuration error: unable to create VIATRA Query runtime logger for engine " + hash);
243 }
244 return logger;
245 }
246
247 ///////////////// internal stuff //////////////
248 private void internalRegisterMatcher(IQuerySpecification<?> querySpecification, ViatraQueryMatcher<?> matcher) {
249 matchers.addPair(querySpecification, matcher);
250 lifecycleProvider.matcherInstantiated(matcher);
251 }
252
253 /**
254 * Provides access to the selected query backend component of the VIATRA Query Engine.
255 */
256 @Override
257 public IQueryBackend getQueryBackend(IQueryBackendFactory iQueryBackendFactory) {
258 IQueryBackend iQueryBackend = queryBackends.get(iQueryBackendFactory);
259 if (iQueryBackend == null) {
260 // do this first, to make sure the runtime context exists
261 final IQueryRuntimeContext queryRuntimeContext = engineContext.getQueryRuntimeContext();
262
263 // maybe the backend has been created in the meantime when the indexer was initialized and queried for
264 // derived features
265 // no need to instantiate a new backend in that case
266 iQueryBackend = queryBackends.get(iQueryBackendFactory);
267 if (iQueryBackend == null) {
268
269 // need to instantiate the backend
270 iQueryBackend = iQueryBackendFactory.create(new IQueryBackendContext() {
271
272 @Override
273 public IQueryRuntimeContext getRuntimeContext() {
274 return queryRuntimeContext;
275 }
276
277 @Override
278 public IQueryCacheContext getQueryCacheContext() {
279 return ViatraQueryEngineImpl.this;
280 }
281
282 @Override
283 public Logger getLogger() {
284 return logger;
285 }
286
287 @Override
288 public IQueryBackendHintProvider getHintProvider() {
289 return ViatraQueryEngineImpl.this;
290 }
291
292 @Override
293 public IQueryResultProviderAccess getResultProviderAccess() {
294 return ViatraQueryEngineImpl.this;
295 }
296
297 @Override
298 public QueryAnalyzer getQueryAnalyzer() {
299 if (queryAnalyzer == null)
300 queryAnalyzer = new QueryAnalyzer(queryRuntimeContext.getMetaContext());
301 return queryAnalyzer;
302 }
303
304 @Override
305 public boolean areUpdatesDelayed() {
306 return ViatraQueryEngineImpl.this.delayMessageDelivery;
307 }
308
309 @Override
310 public IMatcherCapability getRequiredMatcherCapability(PQuery query,
311 QueryEvaluationHint hint) {
312 return engineOptions.getQueryBackendFactory(hint).calculateRequiredCapability(query, hint);
313 }
314
315
316
317 });
318 queryBackends.put(iQueryBackendFactory, iQueryBackend);
319 }
320 }
321 return iQueryBackend;
322 }
323
324 ///////////////// advanced stuff /////////////
325
326 @Override
327 public void dispose() {
328 if (manager != null) {
329 throw new UnsupportedOperationException(
330 String.format("Cannot dispose() managed VIATRA Query Engine. Attempted for scope %s.", scope));
331 }
332 wipe();
333
334 this.disposed = true;
335
336 // called before base index disposal to allow removal of base listeners
337 lifecycleProvider.engineDisposed();
338
339 try {
340 engineContext.dispose();
341 } catch (IllegalStateException ex) {
342 getLogger().warn(
343 "The base index could not be disposed along with the VIATRA Query engine, as there are still active listeners on it.");
344 }
345 }
346
347 @Override
348 public void wipe() {
349 if (manager != null) {
350 throw new UnsupportedOperationException(
351 String.format("Cannot wipe() managed VIATRA Query Engine. Attempted for scope %s.", scope));
352 }
353 if (queryBackends != null) {
354 for (IQueryBackend backend : queryBackends.values()) {
355 backend.dispose();
356 }
357 queryBackends.clear();
358 }
359 matchers.clear();
360 queryAnalyzer = null;
361 lifecycleProvider.engineWiped();
362 }
363
364 /**
365 * Indicates whether the engine is in a tainted, inconsistent state.
366 */
367 private boolean tainted = false;
368 private IIndexingErrorListener taintListener = new SelfTaintListener(this);
369
370 private static class SelfTaintListener implements IIndexingErrorListener {
371 WeakReference<ViatraQueryEngineImpl> queryEngineRef;
372
373 public SelfTaintListener(ViatraQueryEngineImpl queryEngine) {
374 this.queryEngineRef = new WeakReference<ViatraQueryEngineImpl>(queryEngine);
375 }
376
377 public void engineBecameTainted(String description, Throwable t) {
378 final ViatraQueryEngineImpl queryEngine = queryEngineRef.get();
379 if (queryEngine != null) {
380 queryEngine.tainted = true;
381 queryEngine.lifecycleProvider.engineBecameTainted(description, t);
382 }
383 }
384
385 private boolean noTaintDetectedYet = true;
386
387 protected void notifyTainted(String description, Throwable t) {
388 if (noTaintDetectedYet) {
389 noTaintDetectedYet = false;
390 engineBecameTainted(description, t);
391 }
392 }
393
394 @Override
395 public void error(String description, Throwable t) {
396 // Errors does not mean tainting
397 }
398
399 @Override
400 public void fatal(String description, Throwable t) {
401 notifyTainted(description, t);
402 }
403 }
404
405 @Override
406 public boolean isTainted() {
407 return tainted;
408 }
409
410 @Override
411 public boolean isManaged() {
412 return manager != null;
413 // return isAdvanced; ???
414 }
415
416 private <Match extends IPatternMatch> IQueryResultProvider getUnderlyingResultProvider(
417 final BaseMatcher<Match> matcher) {
418 // IQueryResultProvider resultProvider = reteEngine.accessMatcher(matcher.getSpecification());
419 return matcher.backend;
420 }
421
422 @Override
423 public <Match extends IPatternMatch> void addMatchUpdateListener(final ViatraQueryMatcher<Match> matcher,
424 final IMatchUpdateListener<? super Match> listener, boolean fireNow) {
425
426 checkArgument(listener != null, "Cannot add null listener!");
427 checkArgument(matcher.getEngine() == this, "Cannot register listener for matcher of different engine!");
428 checkArgument(!disposed, "Cannot register listener on matcher of disposed engine!");
429
430 final BaseMatcher<Match> bm = (BaseMatcher<Match>) matcher;
431
432 final IUpdateable updateDispatcher = (updateElement, isInsertion) -> {
433 Match match = null;
434 try {
435 match = bm.newMatch(updateElement.getElements());
436 if (isInsertion)
437 listener.notifyAppearance(match);
438 else
439 listener.notifyDisappearance(match);
440 } catch (Throwable e) { // NOPMD
441 if (e instanceof Error)
442 throw (Error) e;
443 logger.warn(
444 String.format(
445 "The incremental pattern matcher encountered an error during %s a callback on %s of match %s of pattern %s. Error message: %s. (Developer note: %s in %s callback)",
446 match == null ? "preparing" : "invoking", isInsertion ? "insertion" : "removal",
447 match == null ? updateElement.toString() : match.prettyPrint(),
448 matcher.getPatternName(), e.getMessage(), e.getClass().getSimpleName(), listener),
449 e);
450 }
451
452 };
453
454 IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm);
455 resultProvider.addUpdateListener(updateDispatcher, listener, fireNow);
456 }
457
458 @Override
459 public <Match extends IPatternMatch> void removeMatchUpdateListener(ViatraQueryMatcher<Match> matcher,
460 IMatchUpdateListener<? super Match> listener) {
461 checkArgument(listener != null, "Cannot remove null listener!");
462 checkArgument(matcher.getEngine() == this, "Cannot remove listener from matcher of different engine!");
463 checkArgument(!disposed, "Cannot remove listener from matcher of disposed engine!");
464
465 final BaseMatcher<Match> bm = (BaseMatcher<Match>) matcher;
466
467 try {
468 IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm);
469 resultProvider.removeUpdateListener(listener);
470 } catch (Exception e) {
471 logger.error(
472 "Error while removing listener " + listener + " from the matcher of " + matcher.getPatternName(),
473 e);
474 }
475 }
476
477 @Override
478 public void addModelUpdateListener(ViatraQueryModelUpdateListener listener) {
479 modelUpdateProvider.addListener(listener);
480 }
481
482 @Override
483 public void removeModelUpdateListener(ViatraQueryModelUpdateListener listener) {
484 modelUpdateProvider.removeListener(listener);
485 }
486
487 @Override
488 public void addLifecycleListener(ViatraQueryEngineLifecycleListener listener) {
489 lifecycleProvider.addListener(listener);
490 }
491
492 @Override
493 public void removeLifecycleListener(ViatraQueryEngineLifecycleListener listener) {
494 lifecycleProvider.removeListener(listener);
495 }
496
497 /**
498 * Returns an internal interface towards the query backend to feed the matcher with results.
499 *
500 * @param query
501 * the pattern for which the result provider should be delivered
502 *
503 * @throws ViatraQueryRuntimeException
504 */
505 public IQueryResultProvider getResultProvider(IQuerySpecification<?> query) {
506 Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE);
507
508 return getResultProviderInternal(query, null);
509 }
510
511 /**
512 * Returns an internal interface towards the query backend to feed the matcher with results.
513 *
514 * @param query
515 * the pattern for which the result provider should be delivered
516 *
517 * @throws ViatraQueryRuntimeException
518 */
519 public IQueryResultProvider getResultProvider(IQuerySpecification<?> query, QueryEvaluationHint hint) {
520 Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE);
521
522 return getResultProviderInternal(query, hint);
523 }
524
525 /**
526 * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use
527 * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to
528 * make sure engine and query specific hints are correctly applied.
529 *
530 * @throws ViatraQueryRuntimeException
531 */
532 private IQueryResultProvider getResultProviderInternal(IQuerySpecification<?> query, QueryEvaluationHint hint) {
533 return getResultProviderInternal(query.getInternalQueryRepresentation(), hint);
534 }
535
536 /**
537 * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use
538 * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to
539 * make sure engine and query specific hints are correctly applied.
540 *
541 * @throws ViatraQueryRuntimeException
542 */
543 private IQueryResultProvider getResultProviderInternal(PQuery query, QueryEvaluationHint hint) {
544 Preconditions.checkArgument(query != null, "Query cannot be null!");
545 Preconditions.checkArgument(query.getStatus() != PQueryStatus.ERROR, "Cannot initialize a result provider for the erronoues query `%s`.", query.getSimpleName());
546 final IQueryBackend backend = getQueryBackend(engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query, hint)));
547 return backend.getResultProvider(query, hint);
548 }
549
550 /**
551 * Returns the query backend (influenced by the hint system), even if it is a non-caching backend.
552 *
553 * @throws ViatraQueryRuntimeException
554 */
555 private IQueryBackend getQueryBackend(PQuery query) {
556 final IQueryBackendFactory factory = engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query));
557 return getQueryBackend(factory);
558 }
559
560 /**
561 * Returns a caching query backend (influenced by the hint system).
562 *
563 * @throws ViatraQueryRuntimeException
564 */
565 private IQueryBackend getCachingQueryBackend(PQuery query) {
566 IQueryBackend regularBackend = getQueryBackend(query);
567 if (regularBackend.isCaching())
568 return regularBackend;
569 else
570 return getQueryBackend(engineOptions.getDefaultCachingBackendFactory());
571 }
572
573 @Override
574 public boolean isResultCached(PQuery query) {
575 try {
576 return null != getCachingQueryBackend(query).peekExistingResultProvider(query);
577 } catch (ViatraQueryException iqe) {
578 getLogger().error(ERROR_ACCESSING_BACKEND, iqe);
579 return false;
580 }
581 }
582
583 @Override
584 public IQueryResultProvider getCachingResultProvider(PQuery query) {
585 try {
586 return getCachingQueryBackend(query).getResultProvider(query);
587 } catch (ViatraQueryException iqe) {
588 getLogger().error(ERROR_ACCESSING_BACKEND, iqe);
589 throw iqe;
590 }
591 }
592
593 private QueryEvaluationHint getEngineDefaultHint() {
594 return engineOptions.getEngineDefaultHints();
595 }
596
597 @Override
598 public QueryEvaluationHint getQueryEvaluationHint(PQuery query) {
599 return getEngineDefaultHint().overrideBy(query.getEvaluationHints());
600 }
601
602 private QueryEvaluationHint getQueryEvaluationHint(IQuerySpecification<?> querySpecification,
603 QueryEvaluationHint optionalOverrideHints) {
604 return getQueryEvaluationHint(querySpecification.getInternalQueryRepresentation())
605 .overrideBy(optionalOverrideHints);
606 }
607
608 private QueryEvaluationHint getQueryEvaluationHint(PQuery query, QueryEvaluationHint optionalOverrideHints) {
609 return getQueryEvaluationHint(query).overrideBy(optionalOverrideHints);
610 }
611
612 private IMatcherCapability getRequestedCapability(IQuerySpecification<?> querySpecification,
613 QueryEvaluationHint optionalOverrideHints) {
614 final QueryEvaluationHint hint = getQueryEvaluationHint(querySpecification, optionalOverrideHints);
615 return engineOptions.getQueryBackendFactory(hint)
616 .calculateRequiredCapability(querySpecification.getInternalQueryRepresentation(), hint);
617 }
618
619 @Override
620 public void prepareGroup(IQueryGroup queryGroup, final QueryEvaluationHint optionalEvaluationHints) {
621 try {
622 Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE);
623
624 final Set<IQuerySpecification<?>> specifications = new HashSet<IQuerySpecification<?>>(
625 queryGroup.getSpecifications());
626 final Collection<PQuery> patterns = specifications.stream().map(
627 IQuerySpecification::getInternalQueryRepresentation).collect(Collectors.toList());
628 patterns.forEach(PQuery::ensureInitialized);
629
630 Collection<String> erroneousPatterns = patterns.stream().
631 filter(PQueries.queryStatusPredicate(PQueryStatus.ERROR)).
632 map(PQuery::getFullyQualifiedName).
633 collect(Collectors.toList());
634 Preconditions.checkState(erroneousPatterns.isEmpty(), "Erroneous query(s) found: %s",
635 erroneousPatterns.stream().collect(Collectors.joining(", ")));
636
637 // TODO maybe do some smarter preparation per backend?
638 try {
639 engineContext.getBaseIndex().coalesceTraversals(new Callable<Void>() {
640 @Override
641 public Void call() throws Exception {
642 for (IQuerySpecification<?> query : specifications) {
643 getResultProviderInternal(query, optionalEvaluationHints);
644 }
645 return null;
646 }
647 });
648 } catch (InvocationTargetException ex) {
649 final Throwable cause = ex.getCause();
650 if (cause instanceof QueryProcessingException)
651 throw (QueryProcessingException) cause;
652 if (cause instanceof ViatraQueryException)
653 throw (ViatraQueryException) cause;
654 if (cause instanceof RuntimeException)
655 throw (RuntimeException) cause;
656 assert (false);
657 }
658 } catch (QueryProcessingException e) {
659 throw new ViatraQueryException(e);
660 }
661 }
662
663 @Override
664 public QueryScope getScope() {
665 return scope;
666 }
667
668 @Override
669 public ViatraQueryEngineOptions getEngineOptions() {
670 return engineOptions;
671 }
672
673 @Override
674 public IQueryResultProvider getResultProviderOfMatcher(ViatraQueryMatcher<? extends IPatternMatch> matcher) {
675 return ((QueryResultWrapper) matcher).backend;
676 }
677
678 @Override
679 public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints) {
680 try {
681 return getResultProviderInternal(query, overrideHints);
682 } catch (ViatraQueryException e) {
683 getLogger().error(ERROR_ACCESSING_BACKEND, e);
684 throw e;
685 }
686 }
687
688 @Override
689 public boolean isDisposed() {
690 return disposed;
691 }
692
693}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java
new file mode 100644
index 00000000..8bbf2a66
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java
@@ -0,0 +1,138 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.engine;
10
11import java.util.ArrayList;
12
13import org.apache.log4j.Logger;
14import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine;
15import tools.refinery.viatra.runtime.api.IPatternMatch;
16import tools.refinery.viatra.runtime.api.ViatraQueryEngineLifecycleListener;
17import tools.refinery.viatra.runtime.api.ViatraQueryMatcher;
18
19public final class LifecycleProvider extends ListenerContainer<ViatraQueryEngineLifecycleListener> implements ViatraQueryEngineLifecycleListener{
20
21 private final Logger logger;
22
23 /**
24 * @param queryEngine
25 */
26 public LifecycleProvider(AdvancedViatraQueryEngine queryEngine, Logger logger) {
27 this.logger = logger;
28 }
29
30 @Override
31 protected void listenerAdded(ViatraQueryEngineLifecycleListener listener) {
32 logger.debug("Lifecycle listener " + listener + " added to engine.");
33 }
34
35 @Override
36 protected void listenerRemoved(ViatraQueryEngineLifecycleListener listener) {
37 logger.debug("Lifecycle listener " + listener + " removed from engine.");
38 }
39
40// public void propagateEventToListeners(Predicate<ViatraQueryEngineLifecycleListener> function) {
41// if (!listeners.isEmpty()) {
42// for (ViatraQueryEngineLifecycleListener listener : new ArrayList<ViatraQueryEngineLifecycleListener>(listeners)) {
43// try {
44// function.apply(listener);
45// } catch (Exception ex) {
46// logger.error(
47// "VIATRA Query encountered an error in delivering notification to listener "
48// + listener + ".", ex);
49// }
50// }
51// }
52// }
53
54 @Override
55 public void matcherInstantiated(final ViatraQueryMatcher<? extends IPatternMatch> matcher) {
56 if (!listeners.isEmpty()) {
57 for (ViatraQueryEngineLifecycleListener listener : new ArrayList<ViatraQueryEngineLifecycleListener>(listeners)) {
58 try {
59 listener.matcherInstantiated(matcher);
60 } catch (Exception ex) {
61 logger.error(
62 "VIATRA Query encountered an error in delivering matcher initialization notification to listener "
63 + listener + ".", ex);
64 }
65 }
66 }
67// propagateEventToListeners(new Predicate<ViatraQueryEngineLifecycleListener>() {
68// public boolean apply(ViatraQueryEngineLifecycleListener listener) {
69// listener.matcherInstantiated(matcher);
70// return true;
71// }
72// });
73 }
74
75 @Override
76 public void engineBecameTainted(String description, Throwable t) {
77 if (!listeners.isEmpty()) {
78 for (ViatraQueryEngineLifecycleListener listener : new ArrayList<ViatraQueryEngineLifecycleListener>(listeners)) {
79 try {
80 listener.engineBecameTainted(description, t);
81 } catch (Exception ex) {
82 logger.error(
83 "VIATRA Query encountered an error in delivering engine tainted notification to listener "
84 + listener + ".", ex);
85 }
86 }
87 }
88// propagateEventToListeners(new Predicate<ViatraQueryEngineLifecycleListener>() {
89// public boolean apply(ViatraQueryEngineLifecycleListener listener) {
90// listener.engineBecameTainted();
91// return true;
92// }
93// });
94 }
95
96 @Override
97 public void engineWiped() {
98 if (!listeners.isEmpty()) {
99 for (ViatraQueryEngineLifecycleListener listener : new ArrayList<ViatraQueryEngineLifecycleListener>(listeners)) {
100 try {
101 listener.engineWiped();
102 } catch (Exception ex) {
103 logger.error(
104 "VIATRA Query encountered an error in delivering engine wiped notification to listener "
105 + listener + ".", ex);
106 }
107 }
108 }
109// propagateEventToListeners(new Predicate<ViatraQueryEngineLifecycleListener>() {
110// public boolean apply(ViatraQueryEngineLifecycleListener listener) {
111// listener.engineWiped();
112// return true;
113// }
114// });
115 }
116
117 @Override
118 public void engineDisposed() {
119 if (!listeners.isEmpty()) {
120 for (ViatraQueryEngineLifecycleListener listener : new ArrayList<ViatraQueryEngineLifecycleListener>(listeners)) {
121 try {
122 listener.engineDisposed();
123 } catch (Exception ex) {
124 logger.error(
125 "VIATRA Query encountered an error in delivering engine disposed notification to listener "
126 + listener + ".", ex);
127 }
128 }
129 }
130// propagateEventToListeners(new Predicate<ViatraQueryEngineLifecycleListener>() {
131// public boolean apply(ViatraQueryEngineLifecycleListener listener) {
132// listener.engineDisposed();
133// return true;
134// }
135// });
136 }
137
138 } \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java
new file mode 100644
index 00000000..34133bed
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.engine;
10
11import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument;
12
13import java.util.HashSet;
14import java.util.Set;
15
16public abstract class ListenerContainer<Listener> {
17
18 protected final Set<Listener> listeners;
19
20 public ListenerContainer() {
21 this.listeners = new HashSet<Listener>();
22 }
23
24 public synchronized void addListener(Listener listener) {
25 checkArgument(listener != null, "Cannot add null listener!");
26 boolean added = listeners.add(listener);
27 if(added) {
28 listenerAdded(listener);
29 }
30 }
31
32 public synchronized void removeListener(Listener listener) {
33 checkArgument(listener != null, "Cannot remove null listener!");
34 boolean removed = listeners.remove(listener);
35 if(removed) {
36 listenerRemoved(listener);
37 }
38 }
39
40 protected abstract void listenerAdded(Listener listener);
41
42 protected abstract void listenerRemoved(Listener listener);
43} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java
new file mode 100644
index 00000000..1f2c27e8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java
@@ -0,0 +1,214 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.engine;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.EnumMap;
15import java.util.HashSet;
16import java.util.Map;
17import java.util.Map.Entry;
18
19import org.apache.log4j.Logger;
20import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine;
21import tools.refinery.viatra.runtime.api.IMatchUpdateListener;
22import tools.refinery.viatra.runtime.api.IPatternMatch;
23import tools.refinery.viatra.runtime.api.ViatraQueryEngineLifecycleListener;
24import tools.refinery.viatra.runtime.api.ViatraQueryMatcher;
25import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener;
26import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener.ChangeLevel;
27import tools.refinery.viatra.runtime.api.scope.ViatraBaseIndexChangeListener;
28import tools.refinery.viatra.runtime.exception.ViatraQueryException;
29import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
30
31public final class ModelUpdateProvider extends ListenerContainer<ViatraQueryModelUpdateListener> {
32
33 private final AdvancedViatraQueryEngine queryEngine;
34 private ChangeLevel currentChange = ChangeLevel.NO_CHANGE;
35 private ChangeLevel maxLevel = ChangeLevel.NO_CHANGE;
36 private final Map<ChangeLevel, Collection<ViatraQueryModelUpdateListener>> listenerMap;
37 private final Logger logger;
38
39 public ModelUpdateProvider(AdvancedViatraQueryEngine queryEngine, Logger logger) {
40 super();
41 this.queryEngine = queryEngine;
42 this.logger = logger;
43 listenerMap = new EnumMap<>(ChangeLevel.class);
44 }
45
46 @Override
47 protected void listenerAdded(ViatraQueryModelUpdateListener listener) {
48 // check ChangeLevel
49 // create callback for given level if required
50 if(listenerMap.isEmpty()) {
51 try {
52 this.queryEngine.getBaseIndex().addBaseIndexChangeListener(indexListener);
53 // add listener to new matchers (use lifecycle listener)
54 this.queryEngine.addLifecycleListener(selfListener);
55 } catch (ViatraQueryException e) {
56 throw new IllegalStateException("Model update listener used on engine without base index", e);
57 }
58 }
59
60 ChangeLevel changeLevel = listener.getLevel();
61 listenerMap.computeIfAbsent(changeLevel, k -> CollectionsFactory.createSet()).add(listener);
62 // increase or keep max level of listeners
63 ChangeLevel oldMaxLevel = maxLevel;
64 maxLevel = maxLevel.changeOccured(changeLevel);
65 if(!maxLevel.equals(oldMaxLevel) && ChangeLevel.MATCHSET.compareTo(oldMaxLevel) > 0 && ChangeLevel.MATCHSET.compareTo(maxLevel) <= 0) {
66 // add matchUpdateListener to all matchers
67 for (ViatraQueryMatcher<?> matcher : this.queryEngine.getCurrentMatchers()) {
68 this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
69 }
70 }
71 }
72
73 @Override
74 protected void listenerRemoved(ViatraQueryModelUpdateListener listener) {
75 ChangeLevel changeLevel = listener.getLevel();
76 Collection<ViatraQueryModelUpdateListener> old = listenerMap.getOrDefault(changeLevel, Collections.emptySet());
77 boolean removed = old.remove(listener);
78 if(removed) {
79 if (old.isEmpty()) listenerMap.remove(changeLevel);
80 } else {
81 handleUnsuccesfulRemove(listener);
82 }
83
84 updateMaxLevel();
85
86 if(listenerMap.isEmpty()) {
87 this.queryEngine.removeLifecycleListener(selfListener);
88 removeBaseIndexChangeListener();
89 }
90 }
91
92 private void removeBaseIndexChangeListener() {
93 try {
94 this.queryEngine.getBaseIndex().removeBaseIndexChangeListener(indexListener);
95 } catch (ViatraQueryException e) {
96 throw new IllegalStateException("Model update listener used on engine without base index", e);
97 }
98 }
99
100 private void updateMaxLevel() {
101 if(!listenerMap.containsKey(maxLevel)) {
102 ChangeLevel newMaxLevel = ChangeLevel.NO_CHANGE;
103 for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) {
104 newMaxLevel = newMaxLevel.changeOccured(level);
105 }
106 maxLevel = newMaxLevel;
107 }
108 if(maxLevel.compareTo(ChangeLevel.MATCHSET) < 0) {
109 // remove listener from matchers
110 for (ViatraQueryMatcher<?> matcher : this.queryEngine.getCurrentMatchers()) {
111 this.queryEngine.removeMatchUpdateListener(matcher, matchSetListener);
112 }
113 }
114 }
115
116 private void handleUnsuccesfulRemove(ViatraQueryModelUpdateListener listener) {
117 for (Entry<ChangeLevel, Collection<ViatraQueryModelUpdateListener>> entry : listenerMap.entrySet()) {
118 Collection<ViatraQueryModelUpdateListener> existingListeners = entry.getValue();
119 // if the listener is contained in some other bucket, remove it from there
120 if(existingListeners.remove(listener)) {
121 logger.error("Listener "+listener+" change level changed since initialization!");
122 if (existingListeners.isEmpty()) listenerMap.remove(entry.getKey());
123 return; // listener is contained only once
124 }
125 }
126 logger.error("Listener "+listener+" already removed from map (e.g. engine was already disposed)!");
127 }
128
129 private void notifyListeners() {
130
131 // any change that occurs after this point should be regarded as a new event
132 // FIXME what should happen when a listener creates new notifications?
133 // -> other listeners will get events in different order
134 ChangeLevel tempLevel = currentChange;
135 currentChange = ChangeLevel.NO_CHANGE;
136
137 if(!listenerMap.isEmpty()) {
138 for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) {
139 if(tempLevel.compareTo(level) >= 0) {
140 for (ViatraQueryModelUpdateListener listener : new ArrayList<>(listenerMap.get(level))) {
141 try {
142 listener.notifyChanged(tempLevel);
143 } catch (Exception ex) {
144 logger.error(
145 "VIATRA Query encountered an error in delivering model update notification to listener "
146 + listener + ".", ex);
147 }
148 }
149 }
150 }
151 } else {
152 throw new IllegalStateException("Notify listeners must not be called without listeners! Maybe an update callback was not removed correctly.");
153 }
154
155 }
156
157 // model update "providers":
158 // - model: IQBase callback even if not dirty
159 // - index: IQBase dirty callback
160 private final ViatraBaseIndexChangeListener indexListener = new ViatraBaseIndexChangeListener() {
161
162 @Override
163 public boolean onlyOnIndexChange() {
164 return false;
165 }
166
167 @Override
168 public void notifyChanged(boolean indexChanged) {
169 if(indexChanged) {
170 currentChange = currentChange.changeOccured(ChangeLevel.INDEX);
171 } else {
172 currentChange = currentChange.changeOccured(ChangeLevel.MODEL);
173 }
174 notifyListeners();
175 }
176
177 };
178 // - matchset: add the same listener to each matcher and use a dirty flag. needs IQBase callback as well
179 private final IMatchUpdateListener<IPatternMatch> matchSetListener = new IMatchUpdateListener<IPatternMatch>() {
180
181 @Override
182 public void notifyDisappearance(IPatternMatch match) {
183 currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
184 }
185
186 @Override
187 public void notifyAppearance(IPatternMatch match) {
188 currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
189 }
190 };
191
192 private final ViatraQueryEngineLifecycleListener selfListener = new ViatraQueryEngineLifecycleListener() {
193
194 @Override
195 public void matcherInstantiated(ViatraQueryMatcher<? extends IPatternMatch> matcher) {
196 if (maxLevel.compareTo(ChangeLevel.MATCHSET) >= 0) {
197 ModelUpdateProvider.this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
198 }
199 }
200
201 @Override
202 public void engineWiped() {}
203
204 @Override
205 public void engineDisposed() {
206 removeBaseIndexChangeListener();
207 listenerMap.clear();
208 maxLevel = ChangeLevel.NO_CHANGE;
209 }
210
211 @Override
212 public void engineBecameTainted(String description, Throwable t) {}
213 };
214}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java
new file mode 100644
index 00000000..83f6f766
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java
@@ -0,0 +1,42 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers;
10
11/**
12 * A common base class for all exceptions thrown by various VIATRA Query Runtime APIs.
13 *
14 * @author Zoltan Ujhelyi
15 * @since 2.0
16 */
17public abstract class ViatraQueryRuntimeException extends RuntimeException {
18
19 private static final long serialVersionUID = -8505253058035069310L;
20
21 public ViatraQueryRuntimeException() {
22 super();
23 }
24
25 public ViatraQueryRuntimeException(String message) {
26 super(message);
27 }
28
29 public ViatraQueryRuntimeException(Throwable cause) {
30 super(cause);
31 }
32
33 public ViatraQueryRuntimeException(String message, Throwable cause) {
34 super(message, cause);
35 }
36
37 public ViatraQueryRuntimeException(String message, Throwable cause, boolean enableSuppression,
38 boolean writableStackTrace) {
39 super(message, cause, enableSuppression, writableStackTrace);
40 }
41
42}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java
new file mode 100644
index 00000000..2c09ede1
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11/**
12 * @since 2.0
13 */
14class AverageAccumulator<Domain> {
15 Domain value;
16 long count;
17
18 public AverageAccumulator(Domain value, long count) {
19 super();
20 this.value = value;
21 this.count = count;
22 }
23
24} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java
new file mode 100644
index 00000000..e8a26afd
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.OptionalDouble;
12import java.util.stream.Stream;
13
14import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15
16/**
17 * @author Zoltan Ujhelyi
18 * @since 2.0
19 */
20public class DoubleAverageOperator implements IMultisetAggregationOperator<Double, AverageAccumulator<Double>, Double> {
21
22 public static final DoubleAverageOperator INSTANCE = new DoubleAverageOperator();
23
24 private DoubleAverageOperator() {
25 // Singleton, do not call.
26 }
27
28 @Override
29 public String getShortDescription() {
30 return "avg<Integer> incrementally computes the average of java.lang.Integer values";
31 }
32
33 @Override
34 public String getName() {
35 return "avg<Integer>";
36 }
37
38 @Override
39 public AverageAccumulator<Double> createNeutral() {
40 return new AverageAccumulator<Double>(0d, 0l);
41 }
42
43 @Override
44 public boolean isNeutral(AverageAccumulator<Double> result) {
45 return result.count == 0l;
46 }
47
48 @Override
49 public AverageAccumulator<Double> update(AverageAccumulator<Double> oldResult, Double updateValue,
50 boolean isInsertion) {
51 if (isInsertion) {
52 oldResult.value += updateValue;
53 oldResult.count++;
54 } else {
55 oldResult.value -= updateValue;
56 oldResult.count--;
57 }
58 return oldResult;
59 }
60
61 @Override
62 public Double getAggregate(AverageAccumulator<Double> result) {
63 return (result.count == 0)
64 ? null
65 : result.value/result.count;
66 }
67
68 @Override
69 public Double aggregateStream(Stream<Double> stream) {
70 final OptionalDouble averageOpt = stream.mapToDouble(Double::doubleValue).average();
71 return averageOpt.isPresent() ? averageOpt.getAsDouble() : null;
72 }
73
74 /**
75 * @since 2.4
76 */
77 @Override
78 public AverageAccumulator<Double> clone(AverageAccumulator<Double> original) {
79 return new AverageAccumulator<Double>(original.value, original.count);
80 }
81
82} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java
new file mode 100644
index 00000000..744b0cd1
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java
@@ -0,0 +1,62 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator;
14
15/**
16 * Incrementally computes the sum of java.lang.Double values
17 * @author Gabor Bergmann
18 * @since 1.4
19 */
20public class DoubleSumOperator extends AbstractMemorylessAggregationOperator<Double, Double> {
21 public static final DoubleSumOperator INSTANCE = new DoubleSumOperator();
22
23 private DoubleSumOperator() {
24 // Singleton, do not call.
25 }
26
27 @Override
28 public String getShortDescription() {
29 return "sum<Double> incrementally computes the sum of java.lang.Double values";
30 }
31 @Override
32 public String getName() {
33 return "sum<Double>";
34 }
35
36 @Override
37 public Double createNeutral() {
38 return 0d;
39 }
40
41 @Override
42 public boolean isNeutral(Double result) {
43 return createNeutral().equals(result);
44 }
45
46 @Override
47 public Double update(Double oldResult, Double updateValue, boolean isInsertion) {
48 return isInsertion ?
49 oldResult + updateValue :
50 oldResult - updateValue;
51 }
52
53 /**
54 * @since 2.0
55 */
56 @Override
57 public Double aggregateStream(Stream<Double> stream) {
58 return stream.mapToDouble(Double::doubleValue).sum();
59 }
60
61
62}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java
new file mode 100644
index 00000000..ee4ceeb8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java
@@ -0,0 +1,135 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.Comparator;
12import java.util.SortedMap;
13import java.util.TreeMap;
14import java.util.stream.Stream;
15
16import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
17
18/**
19 * Incrementally computes the minimum or maximum of java.lang.Comparable values, using the default comparison
20 *
21 * @author Gabor Bergmann
22 * @since 1.4
23 */
24public class ExtremumOperator<T extends Comparable<T>>
25 implements IMultisetAggregationOperator<T, SortedMap<T, Integer>, T> {
26
27 public enum Extreme {
28 MIN, MAX;
29
30 /**
31 * @since 2.0
32 */
33 public <T> T pickFrom(SortedMap<T, Integer> nonEmptyMultiSet) {
34 switch(this) {
35 case MIN:
36 return nonEmptyMultiSet.firstKey();
37 case MAX:
38 return nonEmptyMultiSet.lastKey();
39 default:
40 return null;
41 }
42 }
43 }
44
45 private static final ExtremumOperator MIN_OP = new ExtremumOperator<>(Extreme.MIN);
46 private static final ExtremumOperator MAX_OP = new ExtremumOperator<>(Extreme.MAX);
47
48 public static <T extends Comparable<T>> ExtremumOperator<T> getMin() {
49 return MIN_OP;
50 }
51 public static <T extends Comparable<T>> ExtremumOperator<T> getMax() {
52 return MAX_OP;
53 }
54
55 Extreme extreme;
56 private ExtremumOperator(Extreme extreme) {
57 super();
58 this.extreme = extreme;
59 }
60
61 @Override
62 public String getShortDescription() {
63 String opName = getName();
64 return String.format(
65 "%s incrementally computes the %simum of java.lang.Comparable values, using the default comparison",
66 opName, opName);
67 }
68
69 @Override
70 public String getName() {
71 return extreme.name().toLowerCase();
72 }
73
74 /**
75 * @since 2.0
76 */
77 @Override
78 public SortedMap<T, Integer> createNeutral() {
79 return new TreeMap<>();
80 }
81
82 /**
83 * @since 2.0
84 */
85 @Override
86 public boolean isNeutral(SortedMap<T, Integer> result) {
87 return result.isEmpty();
88 }
89
90 /**
91 * @since 2.0
92 */
93 @Override
94 public SortedMap<T, Integer> update(SortedMap<T, Integer> oldResult, T updateValue, boolean isInsertion) {
95 oldResult.compute(updateValue, (value, c) -> {
96 int count = (c == null) ? 0 : c;
97 int result = (isInsertion) ? count+1 : count-1;
98 return (result == 0) ? null : result;
99 });
100 return oldResult;
101 }
102
103 /**
104 * @since 2.0
105 */
106 @Override
107 public T getAggregate(SortedMap<T, Integer> result) {
108 return result.isEmpty() ? null :
109 extreme.pickFrom(result);
110 }
111
112 /**
113 * @since 2.0
114 */
115 @Override
116 public T aggregateStream(Stream<T> stream) {
117 switch (extreme) {
118 case MIN:
119 return stream.min(Comparator.naturalOrder()).orElse(null);
120 case MAX:
121 return stream.max(Comparator.naturalOrder()).orElse(null);
122 default:
123 return null;
124 }
125 }
126
127 /**
128 * @since 2.4
129 */
130 @Override
131 public SortedMap<T, Integer> clone(SortedMap<T, Integer> original) {
132 return new TreeMap<T, Integer>(original);
133 }
134
135}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java
new file mode 100644
index 00000000..bf422476
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.OptionalDouble;
12import java.util.stream.Stream;
13
14import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15
16/**
17 * @author Zoltan Ujhelyi
18 * @since 2.0
19 */
20public class IntegerAverageOperator implements IMultisetAggregationOperator<Integer, AverageAccumulator<Integer>, Double> {
21
22 public static final IntegerAverageOperator INSTANCE = new IntegerAverageOperator();
23
24 private IntegerAverageOperator() {
25 // Singleton, do not call.
26 }
27
28 @Override
29 public String getShortDescription() {
30 return "avg<Integer> incrementally computes the average of java.lang.Integer values";
31 }
32
33 @Override
34 public String getName() {
35 return "avg<Integer>";
36 }
37
38 @Override
39 public AverageAccumulator<Integer> createNeutral() {
40 return new AverageAccumulator<Integer>(0, 0l);
41 }
42
43 @Override
44 public boolean isNeutral(AverageAccumulator<Integer> result) {
45 return result.count == 0l;
46 }
47
48 @Override
49 public AverageAccumulator<Integer> update(AverageAccumulator<Integer> oldResult, Integer updateValue,
50 boolean isInsertion) {
51 if (isInsertion) {
52 oldResult.value += updateValue;
53 oldResult.count++;
54 } else {
55 oldResult.value -= updateValue;
56 oldResult.count--;
57 }
58 return oldResult;
59 }
60
61 @Override
62 public Double getAggregate(AverageAccumulator<Integer> result) {
63 return (result.count == 0)
64 ? null
65 : ((double)result.value)/result.count;
66 }
67
68 @Override
69 public Double aggregateStream(Stream<Integer> stream) {
70 final OptionalDouble averageOpt = stream.mapToInt(Integer::intValue).average();
71 return averageOpt.isPresent() ? averageOpt.getAsDouble() : null;
72 }
73
74 /**
75 * @since 2.4
76 */
77 @Override
78 public AverageAccumulator<Integer> clone(AverageAccumulator<Integer> original) {
79 return new AverageAccumulator<Integer>(original.value, original.count);
80 }
81
82} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java
new file mode 100644
index 00000000..18584256
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator;
14
15/**
16 * Incrementally computes the sum of java.lang.Integer values
17 * @author Gabor Bergmann
18 * @since 1.4
19 */
20public class IntegerSumOperator extends AbstractMemorylessAggregationOperator<Integer, Integer> {
21 public static final IntegerSumOperator INSTANCE = new IntegerSumOperator();
22
23 private IntegerSumOperator() {
24 // Singleton, do not call.
25 }
26
27 @Override
28 public String getShortDescription() {
29 return "sum<Integer> incrementally computes the sum of java.lang.Integer values";
30 }
31 @Override
32 public String getName() {
33 return "sum<Integer>";
34 }
35
36 @Override
37 public Integer createNeutral() {
38 return 0;
39 }
40
41 @Override
42 public boolean isNeutral(Integer result) {
43 return createNeutral().equals(result);
44 }
45
46 @Override
47 public Integer update(Integer oldResult, Integer updateValue, boolean isInsertion) {
48 return isInsertion ?
49 oldResult + updateValue :
50 oldResult - updateValue;
51 }
52
53 /**
54 * @since 2.0
55 */
56 @Override
57 public Integer aggregateStream(Stream<Integer> stream) {
58 return stream.mapToInt(Integer::intValue).sum();
59 }
60
61}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java
new file mode 100644
index 00000000..d56c9507
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.OptionalDouble;
12import java.util.stream.Stream;
13
14import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
15
16/**
17 * @author Zoltan Ujhelyi
18 * @since 2.0
19 */
20public class LongAverageOperator implements IMultisetAggregationOperator<Long, AverageAccumulator<Long>, Double> {
21
22 public static final LongAverageOperator INSTANCE = new LongAverageOperator();
23
24 private LongAverageOperator() {
25 // Singleton, do not call.
26 }
27
28 @Override
29 public String getShortDescription() {
30 return "avg<Integer> incrementally computes the average of java.lang.Integer values";
31 }
32
33 @Override
34 public String getName() {
35 return "avg<Integer>";
36 }
37
38 @Override
39 public AverageAccumulator<Long> createNeutral() {
40 return new AverageAccumulator<Long>(0l, 0l);
41 }
42
43 @Override
44 public boolean isNeutral(AverageAccumulator<Long> result) {
45 return result.count == 0l;
46 }
47
48 @Override
49 public AverageAccumulator<Long> update(AverageAccumulator<Long> oldResult, Long updateValue,
50 boolean isInsertion) {
51 if (isInsertion) {
52 oldResult.value += updateValue;
53 oldResult.count++;
54 } else {
55 oldResult.value -= updateValue;
56 oldResult.count--;
57 }
58 return oldResult;
59 }
60
61 @Override
62 public Double getAggregate(AverageAccumulator<Long> result) {
63 return (result.count == 0)
64 ? null
65 : ((double)result.value)/result.count;
66 }
67
68 @Override
69 public Double aggregateStream(Stream<Long> stream) {
70 final OptionalDouble averageOpt = stream.mapToLong(Long::longValue).average();
71 return averageOpt.isPresent() ? averageOpt.getAsDouble() : null;
72 }
73
74 /**
75 * @since 2.4
76 */
77 @Override
78 public AverageAccumulator<Long> clone(AverageAccumulator<Long> original) {
79 return new AverageAccumulator<Long>(original.value, original.count);
80 }
81
82} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java
new file mode 100644
index 00000000..29ded090
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator;
14
15/**
16 * Incrementally computes the sum of java.lang.Long values
17 * @author Gabor Bergmann
18 * @since 1.4
19 */
20public class LongSumOperator extends AbstractMemorylessAggregationOperator<Long, Long> {
21 public static final LongSumOperator INSTANCE = new LongSumOperator();
22
23 private LongSumOperator() {
24 // Singleton, do not call.
25 }
26
27 @Override
28 public String getShortDescription() {
29 return "sum<Long> incrementally computes the sum of java.lang.Long values";
30 }
31 @Override
32 public String getName() {
33 return "sum<Long>";
34 }
35
36 @Override
37 public Long createNeutral() {
38 return 0L;
39 }
40
41 @Override
42 public boolean isNeutral(Long result) {
43 return createNeutral().equals(result);
44 }
45
46 @Override
47 public Long update(Long oldResult, Long updateValue, boolean isInsertion) {
48 return isInsertion ?
49 oldResult + updateValue :
50 oldResult - updateValue;
51 }
52
53 /**
54 * @since 2.0
55 */
56 @Override
57 public Long aggregateStream(Stream<Long> stream) {
58 return stream.mapToLong(Long::longValue).sum();
59 }
60
61}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java
new file mode 100644
index 00000000..c25678aa
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType;
12import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory;
14
15/**
16 * This aggregator calculates the average of the values of a selected aggregate parameter of a called pattern. The aggregate
17 * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The
18 * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the
19 * summation.
20 *
21 * @since 2.0
22 *
23 */
24@AggregatorType(
25 parameterTypes = {Integer.class, Double.class, Long.class},
26 returnTypes = {Double.class, Double.class, Double.class})
27public final class avg implements IAggregatorFactory {
28
29 @Override
30 public BoundAggregator getAggregatorLogic(Class<?> domainClass) {
31 if (Integer.class.equals(domainClass))
32 return new BoundAggregator(IntegerAverageOperator.INSTANCE, Integer.class, Double.class);
33 if (Double.class.equals(domainClass))
34 return new BoundAggregator(DoubleAverageOperator.INSTANCE, Double.class, Double.class);
35 if (Long.class.equals(domainClass))
36 return new BoundAggregator(LongAverageOperator.INSTANCE, Long.class, Double.class);
37 else throw new IllegalArgumentException();
38 }
39}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java
new file mode 100644
index 00000000..8310a0ce
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType;
12import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory;
14
15/**
16 * An aggregator to count the number of matches a pattern has. The return of the aggregator is an non-negative integer
17 * number.
18 *
19 * @since 1.4
20 *
21 */
22@AggregatorType(parameterTypes = {Void.class}, returnTypes = {Integer.class})
23public final class count implements IAggregatorFactory {
24
25 @Override
26 public BoundAggregator getAggregatorLogic(Class<?> domainClass) {
27 if (Void.class.equals(domainClass))
28 return new BoundAggregator(null, Void.class, Integer.class);
29 else throw new IllegalArgumentException();
30 }
31
32
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java
new file mode 100644
index 00000000..e0236223
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.math.BigDecimal;
12import java.math.BigInteger;
13import java.util.Calendar;
14import java.util.Date;
15
16import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType;
17import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
18import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory;
19
20/**
21 * This aggregator calculates the maximum value of a selected aggregate parameter of a called pattern. The aggregate
22 * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The
23 * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the
24 * minimum calculation.
25 *
26 * @since 1.4
27 * @author Gabor Bergmann
28 */
29@AggregatorType(
30 // TODO T extends Comparable?
31 parameterTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class,
32 Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class},
33 returnTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class,
34 Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class})
35public final class max implements IAggregatorFactory {
36
37 @Override
38 public BoundAggregator getAggregatorLogic(Class<?> domainClass) {
39 if (Comparable.class.isAssignableFrom(domainClass))
40 return new BoundAggregator(ExtremumOperator.getMax(), domainClass, domainClass);
41 else throw new IllegalArgumentException();
42 }
43
44}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java
new file mode 100644
index 00000000..6408c57b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import java.math.BigDecimal;
12import java.math.BigInteger;
13import java.util.Calendar;
14import java.util.Date;
15
16import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType;
17import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
18import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory;
19
20/**
21 * This aggregator calculates the minimum value of a selected aggregate parameter of a called pattern. The aggregate
22 * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The
23 * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the
24 * minimum calculation.
25 *
26 * @since 1.4
27 *
28 */
29@AggregatorType(
30 // TODO T extends Comparable?
31 parameterTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class,
32 Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class},
33 returnTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class,
34 Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class})
35public final class min implements IAggregatorFactory {
36
37 @Override
38 public BoundAggregator getAggregatorLogic(Class<?> domainClass) {
39 if (Comparable.class.isAssignableFrom(domainClass))
40 return new BoundAggregator(ExtremumOperator.getMin(), domainClass, domainClass);
41 else throw new IllegalArgumentException();
42 }
43
44}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java
new file mode 100644
index 00000000..69ff2e75
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.aggregators;
10
11import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType;
12import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
13import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory;
14
15/**
16 * This aggregator calculates the sum of the values of a selected aggregate parameter of a called pattern. The aggregate
17 * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The
18 * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the
19 * summation.
20 *
21 * @since 1.4
22 *
23 */
24@AggregatorType(
25 parameterTypes = {Integer.class, Double.class, Long.class},
26 returnTypes = {Integer.class, Double.class, Long.class})
27public final class sum implements IAggregatorFactory {
28
29 @Override
30 public BoundAggregator getAggregatorLogic(Class<?> domainClass) {
31 if (Integer.class.equals(domainClass))
32 return new BoundAggregator(IntegerSumOperator.INSTANCE, Integer.class, Integer.class);
33 if (Double.class.equals(domainClass))
34 return new BoundAggregator(DoubleSumOperator.INSTANCE, Double.class, Double.class);
35 if (Long.class.equals(domainClass))
36 return new BoundAggregator(LongSumOperator.INSTANCE, Long.class, Long.class);
37 else throw new IllegalArgumentException();
38 }
39}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java
new file mode 100644
index 00000000..1917e909
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.algorithms;
10
11import java.util.Comparator;
12import java.util.Iterator;
13import java.util.NoSuchElementException;
14
15/**
16 * @author Gabor Bergmann
17 * @since 2.1
18 *
19 */
20public class OrderedIterableMerge {
21
22 private OrderedIterableMerge() {
23 // Hidden utility class constructor
24 }
25
26 /**
27 * Lazily merges two iterables, each ordered according to a given comparator.
28 * Retains order in the result, and also eliminates any duplicates that appear in both arguments.
29 */
30 public static <T> Iterable<T> mergeUniques(Iterable<T> first, Iterable<T> second, Comparator<T> comparator) {
31 return () -> new Iterator<T>() {
32 Iterator<T> firstIterator = first.iterator();
33 Iterator<T> secondIterator = second.iterator();
34 T firstItem;
35 T secondItem;
36
37 {
38 fetchFirst();
39 fetchSecond();
40 }
41
42 private T fetchFirst() {
43 T previous = firstItem;
44 if (firstIterator.hasNext())
45 firstItem = firstIterator.next();
46 else
47 firstItem = null;
48 return previous;
49 }
50 private T fetchSecond() {
51 T previous = secondItem;
52 if (secondIterator.hasNext())
53 secondItem = secondIterator.next();
54 else
55 secondItem = null;
56 return previous;
57 }
58
59 @Override
60 public boolean hasNext() {
61 return firstItem != null || secondItem != null;
62 }
63 @Override
64 public T next() {
65 if (!hasNext()) throw new NoSuchElementException();
66 if (firstItem != null && secondItem != null) {
67 if (secondItem == firstItem) { // duplicates
68 fetchFirst();
69 return fetchSecond();
70 } else if (comparator.compare(firstItem, secondItem) < 0) {
71 return fetchFirst();
72 } else {
73 return fetchSecond();
74 }
75 } else if (firstItem != null) {
76 return fetchFirst();
77 } else { // secondItem must be non-null
78 return fetchSecond();
79 }
80 }
81 };
82 }
83}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java
new file mode 100644
index 00000000..c69f08e5
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java
@@ -0,0 +1,214 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.algorithms;
11
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.Map;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
19
20/**
21 * Union-find data structure implementation. Note that the implementation relies on the correct implementation of the
22 * equals method of the type parameter's class.
23 *
24 * @author Tamas Szabo
25 *
26 * @param <V>
27 * the type parameter of the element's stored in the union-find data structure
28 */
29public class UnionFind<V> {
30
31 private final Map<V, UnionFindNodeProperty<V>> nodeMap;
32 final Map<V, Set<V>> setMap;
33
34 /**
35 * Instantiate a new union-find data structure.
36 */
37 public UnionFind() {
38 nodeMap = CollectionsFactory.createMap();
39 setMap = CollectionsFactory.createMap();
40 }
41
42 /**
43 * Instantiate a new union-find data structure with the given elements as separate sets.
44 */
45 public UnionFind(Iterable<V> elements) {
46 this();
47 for (V element : elements) {
48 makeSet(element);
49 }
50 }
51
52 /**
53 * Creates a new union set from a collection of elements.
54 *
55 * @param nodes
56 * the collection of elements
57 * @return the root element
58 */
59 public V makeSet(Collection<V> nodes) {
60 if (!nodes.isEmpty()) {
61 Iterator<V> iterator = nodes.iterator();
62 V root = makeSet(iterator.next());
63 while (iterator.hasNext()) {
64 root = union(root, iterator.next());
65 }
66 return root;
67 } else {
68 return null;
69 }
70 }
71
72 /**
73 * This method creates a single set containing the given node.
74 *
75 * @param node
76 * the root node of the set
77 * @return the root element
78 */
79 public V makeSet(V node) {
80 if (!nodeMap.containsKey(node)) {
81 UnionFindNodeProperty<V> prop = new UnionFindNodeProperty<V>(0, node);
82 nodeMap.put(node, prop);
83 Set<V> set = new HashSet<V>();
84 set.add(node);
85 setMap.put(node, set);
86 }
87 return node;
88 }
89
90 /**
91 * Find method with path compression.
92 *
93 * @param node
94 * the node to find
95 * @return the root node of the set in which the given node can be found
96 */
97 public V find(V node) {
98 UnionFindNodeProperty<V> prop = nodeMap.get(node);
99
100 if (prop != null) {
101 if (prop.parent.equals(node)) {
102 return node;
103 } else {
104 prop.parent = find(prop.parent);
105 return prop.parent;
106 }
107 }
108 return null;
109 }
110
111 /**
112 * Union by rank implementation of the two sets which contain x and y; x and/or y can be a single element from the
113 * universe.
114 *
115 * @param x
116 * set or single element of the universe
117 * @param y
118 * set or single element of the universe
119 * @return the new root of the two sets
120 */
121 public V union(V x, V y) {
122 V xRoot = find(x);
123 V yRoot = find(y);
124
125 if ((xRoot == null) || (yRoot == null)) {
126 return union( (xRoot == null) ? makeSet(x) : xRoot, (yRoot == null) ? makeSet(y) : yRoot);
127 }
128 else if (!xRoot.equals(yRoot)) {
129 UnionFindNodeProperty<V> xRootProp = nodeMap.get(xRoot);
130 UnionFindNodeProperty<V> yRootProp = nodeMap.get(yRoot);
131
132 if (xRootProp.rank < yRootProp.rank) {
133 xRootProp.parent = yRoot;
134 setMap.get(yRoot).addAll(setMap.get(xRoot));
135 setMap.remove(xRoot);
136 return yRoot;
137 } else {// (xRootProp.rank >= yRootProp.rank)
138 yRootProp.parent = xRoot;
139 yRootProp.rank = (xRootProp.rank == yRootProp.rank) ? yRootProp.rank + 1 : yRootProp.rank;
140 setMap.get(xRoot).addAll(setMap.get(yRoot));
141 setMap.remove(yRoot);
142 return xRoot;
143 }
144 } else {
145 return xRoot;
146 }
147 }
148
149 /**
150 * Places the given elements in to the same partition.
151 */
152 public void unite(Set<V> elements) {
153 if (elements.size() > 1) {
154 V current = null;
155 for (V element : elements) {
156 if (current != null) {
157 if (getPartition(element) != null) {
158 union(current, element);
159 }
160 } else {
161 if (getPartition(element) != null) {
162 current = element;
163 }
164 }
165 }
166 }
167 }
168
169 /**
170 * Delete the set whose root is the given node.
171 *
172 * @param root
173 * the root node
174 */
175 public void deleteSet(V root) {
176 // if (setMap.containsKey(root))
177 for (V n : setMap.get(root)) {
178 nodeMap.remove(n);
179 }
180 setMap.remove(root);
181 }
182
183 /**
184 * Returns if all given elements are in the same partition.
185 */
186 public boolean isSameUnion(Set<V> elements) {
187 for (Set<V> partition : setMap.values()) {
188 if (partition.containsAll(elements)) {
189 return true;
190 }
191 }
192 return false;
193 }
194
195
196 /**
197 * Returns the partition in which the given element can be found, or null otherwise.
198 */
199 public Set<V> getPartition(V element) {
200 V root = find(element);
201 return setMap.get(root);
202 }
203
204 /**
205 * Returns all partitions.
206 */
207 public Collection<Set<V>> getPartitions() {
208 return setMap.values();
209 }
210
211 public Set<V> getPartitionHeads() {
212 return setMap.keySet();
213 }
214}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java
new file mode 100644
index 00000000..82852f9c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.algorithms;
11
12public class UnionFindNodeProperty<V> {
13
14 public int rank;
15 public V parent;
16
17 public UnionFindNodeProperty() {
18 this.rank = 0;
19 this.parent = null;
20 }
21
22 public UnionFindNodeProperty(int rank, V parent) {
23 super();
24 this.rank = rank;
25 this.parent = parent;
26 }
27
28 @Override
29 public String toString() {
30 return "[rank:" + rank + ", parent:" + parent.toString() + "]";
31 }
32}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java
new file mode 100644
index 00000000..317293bf
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IRewriterTraceCollector;
12import tools.refinery.viatra.runtime.matchers.psystem.rewriters.NopTraceCollector;
13
14/**
15 * Query evaluation hints applicable to any engine
16 * @since 1.6
17 *
18 */
19public final class CommonQueryHintOptions {
20
21 private CommonQueryHintOptions() {
22 // Hiding constructor for utility class
23 }
24
25 /**
26 * This hint instructs the query backends to record trace information into the given trace collector
27 */
28 public static final QueryHintOption<IRewriterTraceCollector> normalizationTraceCollector =
29 hintOption("normalizationTraceCollector", NopTraceCollector.INSTANCE);
30
31 // internal helper for conciseness
32 private static <T> QueryHintOption<T> hintOption(String hintKeyLocalName, T defaultValue) {
33 return new QueryHintOption<>(CommonQueryHintOptions.class, hintKeyLocalName, defaultValue);
34 }
35
36}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java
new file mode 100644
index 00000000..40853f46
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess;
12import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
13import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
14
15/**
16 * Function object that specifies how hints (including backend preferences) shall propagate through pattern calls.
17 *
18 * <p> A few useful default implementations are included as static fields.
19 *
20 * <p> As of 2.1, only suppported by the local search backend, and only if the pattern call is not flattened.
21 *
22 * @author Gabor Bergmann
23 * @since 2.1
24 */
25@FunctionalInterface
26public interface ICallDelegationStrategy {
27
28 /**
29 * Specifies how hints (including backend preferences) shall propagate through pattern calls.
30 *
31 * @param call a {@link PConstraint} in a query that calls another query.
32 * @param callerHint a hint under which the calling pattern is evaluated,
33 * @param callerBackend the actual backend evaluating the calling pattern.
34 * @param calleeHintProvider the provider of hints for the called pattern.
35 * @return the hints, including backend selection,
36 * that the backend responsible for the caller pattern must specify when
37 * requesting the {@link IQueryResultProvider} for the called pattern via {@link IQueryResultProviderAccess}.
38 */
39 public QueryEvaluationHint transformHints(IQueryReference call,
40 QueryEvaluationHint callerHint,
41 IQueryBackend callerBackend,
42 IQueryBackendHintProvider calleeHintProvider);
43
44
45 /**
46 * Options known for callee are used to override caller options, except the backend selection.
47 * Always use the same backend for the callee and the caller, regardless what is specified for the callee pattern.
48 * @author Gabor Bergmann
49 */
50 public static final ICallDelegationStrategy FULL_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> {
51 QueryEvaluationHint calleeHint =
52 calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery());
53 QueryEvaluationHint result =
54 callerHint == null ? calleeHint : callerHint.overrideBy(calleeHint);
55
56 QueryEvaluationHint backendAdhesion = new QueryEvaluationHint(
57 null /* settings-ignorant */, callerBackend.getFactory());
58 result = result.overrideBy(backendAdhesion);
59 return result;
60 };
61 /**
62 * Options known for callee are used to override caller options, including the backend selection.
63 * If callee does not specify a backend requirement, the backend of the caller is kept.
64 * @author Gabor Bergmann
65 */
66 public static final ICallDelegationStrategy PARTIAL_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> {
67 QueryEvaluationHint backendAdhesion = new QueryEvaluationHint(
68 null /* settings-ignorant */, callerBackend.getFactory());
69
70 QueryEvaluationHint result =
71 callerHint == null ? backendAdhesion : callerHint.overrideBy(backendAdhesion);
72
73 QueryEvaluationHint calleeHint = calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery());
74 result = result.overrideBy(calleeHint);
75
76 return result;
77 };
78 /**
79 * Options known for callee are used to override caller options, including the backend selection.
80 * Always use the backend specified for the callee (or the default if none), regardless of the backend of the caller.
81 * @author Gabor Bergmann
82 */
83 public static final ICallDelegationStrategy NO_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> {
84 QueryEvaluationHint calleeHint = calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery());
85 return callerHint == null ? calleeHint : callerHint.overrideBy(calleeHint);
86 };
87
88
89}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java
new file mode 100644
index 00000000..104b68a8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11/**
12 * Implementations of this interface can be used to decide whether a matcher created by an arbitrary backend can
13 * potentially be used as a substitute for another matcher.
14 *
15 * @author Grill Balázs
16 * @since 1.4
17 *
18 */
19public interface IMatcherCapability {
20
21 /**
22 * Returns true if matchers of this capability can be used as a substitute for a matcher implementing the given capability
23 */
24 public boolean canBeSubstitute(IMatcherCapability capability);
25
26}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java
new file mode 100644
index 00000000..c85f10a4
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java
@@ -0,0 +1,68 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
13
14/**
15 * Internal interface for a VIATRA query specification. Each query is associated with a pattern. Methods instantiate a matcher
16 * of the pattern with various parameters.
17 *
18 * @author Bergmann Gábor
19 * @since 0.9
20 * @noextend This interface is not intended to be extended by users of the VIATRA framework, and should only be used by the query engine
21 */
22public interface IQueryBackend {
23
24 /**
25 * @return true iff this backend is incremental, i.e. it caches the results of queries for quick retrieval,
26 * and can provide update notifications on result set changes.
27 */
28 public boolean isCaching();
29
30 /**
31 * Returns a result provider for a given query. Repeated calls may return the same instance.
32 * @throws ViatraQueryRuntimeException
33 */
34 public IQueryResultProvider getResultProvider(PQuery query);
35
36 /**
37 * Returns a result provider for a given query. Repeated calls may return the same instance.
38 * @param optional hints that may override engine and query defaults (as provided by {@link IQueryBackendHintProvider}). Can be null.
39 * @throws ViatraQueryRuntimeException
40 * @since 1.4
41 */
42 public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints);
43
44 /**
45 * Returns an existing result provider for a given query, if it was previously constructed, returns null otherwise.
46 * Will not construct and initialize new result providers.
47 */
48 public IQueryResultProvider peekExistingResultProvider(PQuery query);
49
50 /**
51 * Propagates all pending updates in this query backend. The implementation of this method is optional, and it
52 * can be ignored entirely if the backend does not delay updates.
53 * @since 1.6
54 */
55 public void flushUpdates();
56
57 /**
58 * Disposes the query backend.
59 */
60 public abstract void dispose();
61
62 /**
63 * @return the factory that created this backend, if this functionality is supported
64 * @since 2.1
65 */
66 public IQueryBackendFactory getFactory();
67
68}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java
new file mode 100644
index 00000000..e264ab3f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java
@@ -0,0 +1,52 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
12import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
13
14/**
15 * A Query Backend Factory identifies a query evaluator implementation, and can create an evaluator instance (an {@link IQueryBackend}) tied to a specific VIATRA Query engine upon request.
16 *
17 * <p> The factory is used as a lookup key for the backend instance,
18 * therefore implementors should either be singletons, or implement equals() / hashCode() accordingly.
19 *
20 * @author Bergmann Gabor
21 *
22 */
23public interface IQueryBackendFactory {
24
25 /**
26 * Creates a new {@link IQueryBackend} instance tied to the given context elements.
27 *
28 * @return an instance of the class returned by {@link #getBackendClass()} that operates in the given context.
29 * @since 1.5
30 */
31 public IQueryBackend
32 create(IQueryBackendContext context);
33
34
35 /**
36 * The backend instances created by this factory are guaranteed to conform to the returned class.
37 */
38 public Class<? extends IQueryBackend> getBackendClass();
39
40 /**
41 * Calculate the required capabilities, which are needed to execute the given pattern
42 *
43 * @since 1.4
44 */
45 public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint);
46
47 /**
48 * Returns whether the current backend is caching
49 * @since 2.0
50 */
51 public boolean isCaching();
52}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java
new file mode 100644
index 00000000..8787814e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11/**
12 * A provider interface for {@link IQueryBackendFactory} instances.
13 * @since 2.0
14 */
15public interface IQueryBackendFactoryProvider {
16
17 /**
18 * Returns a query backend factory instance. The method should return the same instance in case of repeated calls.
19 */
20 IQueryBackendFactory getFactory();
21
22 /**
23 * Returns whether the given query backend should be considered as system default. If multiple backends are
24 * registered as system default, it is undefined which one will be chosen.
25 */
26 default boolean isSystemDefaultEngine() {
27 return false;
28 }
29
30 /**
31 * Returns whether the given query backend should be considered as system default search backend. If multiple
32 * backends are registered as system default, it is undefined which one will be chosen.
33 */
34 default boolean isSystemDefaultSearchBackend() {
35 return false;
36 }
37
38
39 /**
40 * Returns whether the given query backend should be considered as system default caching backend. If multiple
41 * backends are registered as system default, it is undefined which one will be chosen.
42 */
43 default boolean isSystemDefaultCachingBackend() {
44 return false;
45 }
46}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java
new file mode 100644
index 00000000..9bb76349
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
12
13/**
14 * Provides query evaluation hints consisting of the Engine default hints and
15 * the hints provided by the pattern itself.
16 *
17 * @author Bergmann Gabor
18 * @since 0.9
19 * @noimplement This interface is not intended to be implemented by clients, except in the tools.refinery.viatra.runtime plugin.
20 */
21public interface IQueryBackendHintProvider {
22
23 /**
24 * Suggests query evaluation hints regarding a query. The returned hints reflects the default hints of the
25 * query engine merged with the hints provided by the pattern itself. These can be overridden via specific
26 * advanced API of the engine.
27 *
28 * @since 1.4
29 */
30 QueryEvaluationHint getQueryEvaluationHint(PQuery query);
31
32}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java
new file mode 100644
index 00000000..cd7d050f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java
@@ -0,0 +1,202 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import java.util.Optional;
12import java.util.stream.Stream;
13
14import tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper;
15import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.util.Accuracy;
19
20/**
21 * An internal interface of the query backend that provides results of a given query.
22 * @author Bergmann Gabor
23 * @noimplement This interface is not intended to be implemented by clients.
24 */
25public interface IQueryResultProvider {
26
27 /**
28 * Decides whether there are any matches of the pattern that conform to the given fixed values of some parameters.
29 *
30 * @param parameters
31 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
32 * @pre size of input array must be equal to the number of parameters.
33 * @since 2.0
34 */
35 public boolean hasMatch(Object[] parameters);
36
37 /**
38 * Decides whether there are any matches of the pattern that conform to the given fixed values of some parameters.
39 *
40 * @param parameterSeedMask
41 * a mask that extracts those parameters of the query (from the entire parameter list) that should be
42 * bound to a fixed value
43 * @param parameters
44 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
45 * parameterSeedMask, so that for each considered match tuple,
46 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold
47 * @since 2.0
48 */
49 public boolean hasMatch(TupleMask parameterSeedMask, ITuple projectedParameterSeed);
50
51 /**
52 * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters.
53 *
54 * @param parameters
55 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
56 * @pre size of input array must be equal to the number of parameters.
57 * @return the number of pattern matches found.
58 */
59 public int countMatches(Object[] parameters);
60
61 /**
62 * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters.
63 *
64 * @param parameterSeedMask
65 * a mask that extracts those parameters of the query (from the entire parameter list) that should be
66 * bound to a fixed value
67 * @param parameters
68 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
69 * parameterSeedMask, so that for each considered match tuple,
70 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold
71 * @return the number of pattern matches found.
72 * @since 1.7
73 */
74 public int countMatches(TupleMask parameterSeedMask, ITuple projectedParameterSeed);
75
76 /**
77 * Gives an estimate of the number of different groups the matches are projected into by the given mask
78 * (e.g. for an identity mask, this means the full match set size). The estimate must meet the required accuracy.
79 *
80 * <p> If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned.
81 * In other words, query backends may deny an answer, or do their best to give an estimate without actually determining the match set of the query.
82 * However, caching backends are expected to simply return the indexed (projection) size, initialized on-demand if necessary.
83 *
84 * <p> PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask.
85 *
86 * @return if available, an estimate of the cardinality of the projection of the match set, with the desired accuracy.
87 *
88 * @since 2.1
89 */
90 public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy);
91
92 /**
93 * Gives an estimate of the average size of different groups the matches are projected into by the given mask
94 * (e.g. for an identity mask, this means 1, while for an empty mask, the result is match set size).
95 * The estimate must meet the required accuracy.
96 *
97 * <p> If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned.
98 * In other words, query backends may deny an answer, or do their best to give an estimate without actually determining the match set of the query.
99 * However, caching backends are expected to simply return the exact value from the index, initialized on-demand if necessary.
100 *
101 * <p> For an empty match set, zero is acceptable as an exact answer.
102 *
103 * <p> PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask.
104 *
105 * @return if available, an estimate of the average size of each projection group of the match set, with the desired accuracy.
106 *
107 * @since 2.1
108 */
109 public default Optional<Double> estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) {
110 return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy, this::estimateCardinality);
111 }
112
113 /**
114 * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters.
115 * Neither determinism nor randomness of selection is guaranteed.
116 *
117 * @param parameters
118 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
119 * @pre size of input array must be equal to the number of parameters.
120 * @return a match represented in the internal {@link Tuple} representation.
121 * @since 2.0
122 */
123 public Optional<Tuple> getOneArbitraryMatch(Object[] parameters);
124
125 /**
126 * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters.
127 * Neither determinism nor randomness of selection is guaranteed.
128 *
129 * @param parameterSeedMask
130 * a mask that extracts those parameters of the query (from the entire parameter list) that should be
131 * bound to a fixed value
132 * @param parameters
133 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
134 * parameterSeedMask, so that for each considered match tuple,
135 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold
136 * @return a match represented in the internal {@link Tuple} representation.
137 * @since 2.0
138 */
139 public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters);
140
141 /**
142 * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters.
143 *
144 * @param parameters
145 * array where each non-null element binds the corresponding pattern parameter to a fixed value.
146 * @pre size of input array must be equal to the number of parameters.
147 * @return matches represented in the internal {@link Tuple} representation.
148 * @since 2.0
149 */
150 public Stream<Tuple> getAllMatches(Object[] parameters);
151
152 /**
153 * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters.
154 *
155 * @param parameterSeedMask
156 * a mask that extracts those parameters of the query (from the entire parameter list) that should be
157 * bound to a fixed value
158 * @param parameters
159 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
160 * parameterSeedMask, so that for each considered match tuple,
161 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold
162 * @return matches represented in the internal {@link Tuple} representation.
163 * @since 2.0
164 */
165 public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters);
166
167 /**
168 * The underlying query evaluator backend.
169 */
170 public IQueryBackend getQueryBackend();
171
172 /**
173 * Internal method that registers low-level callbacks for match appearance and disappearance.
174 *
175 * <p>
176 * <b>Caution: </b> This is a low-level callback that is invoked when the pattern matcher is not necessarily in a
177 * consistent state yet. Importantly, no model modification permitted during the callback.
178 *
179 * <p>
180 * The callback can be unregistered via invoking {@link #removeUpdateListener(Object)} with the same tag.
181 *
182 * @param listener
183 * the listener that will be notified of each new match that appears or disappears, starting from now.
184 * @param listenerTag
185 * a tag by which to identify the listener for later removal by {@link #removeUpdateListener(Object)}.
186 * @param fireNow
187 * if true, the insertion update allback will be immediately invoked on all current matches as a one-time effect.
188 *
189 * @throws UnsupportedOperationException if this is a non-incremental backend
190 * (i.e. {@link IQueryBackend#isCaching()} on {@link #getQueryBackend()} returns false)
191 */
192 public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow);
193
194 /**
195 * Removes an existing listener previously registered with the given tag.
196 *
197 * @throws UnsupportedOperationException if this is a non-incremental backend
198 * (i.e. {@link IQueryBackend#isCaching()} on {@link #getQueryBackend()} returns false)
199 */
200 public void removeUpdateListener(final Object listenerTag);
201
202}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java
new file mode 100644
index 00000000..baf7144a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12
13/**
14 * Internal interface for the query backend to singal an update to a query result.
15 * @author Bergmann Gabor
16 * @since 0.9
17 *
18 */
19public interface IUpdateable {
20
21 /**
22 * This callback method must be free of exceptions, even {@link RuntimeException}s (though not {@link Error}s).
23 * @param updateElement the tuple that is changed
24 * @param isInsertion true if the tuple appeared in the result set, false if disappeared from the result set
25 */
26 public void update(Tuple updateElement, boolean isInsertion);
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java
new file mode 100644
index 00000000..eab92128
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java
@@ -0,0 +1,241 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import java.util.AbstractMap;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.Map;
15import java.util.Objects;
16import java.util.stream.Collectors;
17
18import tools.refinery.viatra.runtime.matchers.util.Preconditions;
19
20/**
21 * Provides VIATRA Query with additional hints on how a query should be evaluated. The same hint can be provided to multiple queries.
22 *
23 * <p> This class is immutable. Overriding options will create a new instance.
24 *
25 * <p>
26 * Here be dragons: for advanced users only.
27 *
28 * @author Bergmann Gabor
29 *
30 */
31public class QueryEvaluationHint {
32
33 /**
34 * @since 2.0
35 *
36 */
37 public enum BackendRequirement {
38 /**
39 * The current hint does not specify any backend requirement
40 */
41 UNSPECIFIED,
42 /**
43 * The current hint specifies that the default search backend of the engine should be used
44 */
45 DEFAULT_SEARCH,
46 /**
47 * The current hint specifies that the default caching backend of the engine should be used
48 */
49 DEFAULT_CACHING,
50 /**
51 * The current hint specifies that a specific backend is to be used
52 */
53 SPECIFIC
54 }
55
56 final IQueryBackendFactory queryBackendFactory;
57 final Map<QueryHintOption<?>, Object> backendHintSettings;
58 final BackendRequirement requirement;
59
60 /**
61 * Specifies the suggested query backend requirements, and value settings for additional backend-specific options.
62 *
63 * <p>
64 * The backend requirement type must not be {@link BackendRequirement#SPECIFIC} - for that case, use the constructor
65 * {@link #QueryEvaluationHint(Map, IQueryBackendFactory)}.
66 *
67 * @param backendHintSettings
68 * if non-null, each entry in the map overrides backend-specific options regarding query evaluation
69 * (null-valued map entries permitted to erase hints); passing null means default options associated with
70 * the query
71 * @param backendRequirementType
72 * defines the kind of backend requirement
73 * @since 2.0
74 */
75 public QueryEvaluationHint(Map<QueryHintOption<?>, Object> backendHintSettings, BackendRequirement backendRequirementType) {
76 super();
77 Preconditions.checkArgument(backendRequirementType != null, "Specific requirement needs to be set");
78 Preconditions.checkArgument(backendRequirementType != BackendRequirement.SPECIFIC, "Specific backend requirement needs providing a corresponding backend type");
79 this.queryBackendFactory = null;
80 this.requirement = backendRequirementType;
81 this.backendHintSettings = (backendHintSettings == null)
82 ? Collections.<QueryHintOption<?>, Object> emptyMap()
83 : new HashMap<>(backendHintSettings);
84 }
85
86 /**
87 * Specifies the suggested query backend, and value settings for additional backend-specific options. The first
88 * parameter can be null; if the second parameter is null, it is expected that the other constructor is called
89 * instead with a {@link BackendRequirement#UNSPECIFIED} parameter.
90 *
91 * @param backendHintSettings
92 * if non-null, each entry in the map overrides backend-specific options regarding query evaluation
93 * (null-valued map entries permitted to erase hints); passing null means default options associated with
94 * the query
95 * @param queryBackendFactory
96 * overrides the query evaluator algorithm; passing null retains the default algorithm associated with
97 * the query
98 * @since 1.5
99 */
100 public QueryEvaluationHint(
101 Map<QueryHintOption<?>, Object> backendHintSettings,
102 IQueryBackendFactory queryBackendFactory) {
103 super();
104 this.queryBackendFactory = queryBackendFactory;
105 this.requirement = (queryBackendFactory == null) ? BackendRequirement.UNSPECIFIED : BackendRequirement.SPECIFIC;
106 this.backendHintSettings = (backendHintSettings == null)
107 ? Collections.<QueryHintOption<?>, Object> emptyMap()
108 : new HashMap<>(backendHintSettings);
109 }
110
111 /**
112 * Returns the backend requirement described by this hint. If a specific backend is required, that can be queried by {@link #getQueryBackendFactory()}.
113 * @since 2.0
114 */
115 public BackendRequirement getQueryBackendRequirementType() {
116 return requirement;
117 }
118
119 /**
120 * A suggestion for choosing the query evaluator algorithm.
121 *
122 * <p>
123 * Returns null iff {@link #getQueryBackendRequirementType()} does not return {@link BackendRequirement#SPECIFIC};
124 * in such cases a corresponding default backend is selected inside the engine
125 */
126 public IQueryBackendFactory getQueryBackendFactory() {
127 return queryBackendFactory;
128 }
129
130 /**
131 * Each entry in the immutable map overrides backend-specific options regarding query evaluation.
132 *
133 * <p>The map is non-null, even if empty.
134 * Null-valued map entries are also permitted to erase hints via {@link #overrideBy(QueryEvaluationHint)}.
135 *
136 * @since 1.5
137 */
138 public Map<QueryHintOption<?>, Object> getBackendHintSettings() {
139 return backendHintSettings;
140 }
141
142
143 /**
144 * Override values in this hint and return a consolidated instance.
145 *
146 * @since 1.4
147 */
148 public QueryEvaluationHint overrideBy(QueryEvaluationHint overridingHint){
149 if (overridingHint == null)
150 return this;
151
152 BackendRequirement overriddenRequirement = this.getQueryBackendRequirementType();
153 if (overridingHint.getQueryBackendRequirementType() != BackendRequirement.UNSPECIFIED) {
154 overriddenRequirement = overridingHint.getQueryBackendRequirementType();
155 }
156 Map<QueryHintOption<?>, Object> hints = new HashMap<>(this.getBackendHintSettings());
157 if (overridingHint.getBackendHintSettings() != null) {
158 hints.putAll(overridingHint.getBackendHintSettings());
159 }
160 if (overriddenRequirement == BackendRequirement.SPECIFIC) {
161 IQueryBackendFactory factory = this.getQueryBackendFactory();
162 if (overridingHint.getQueryBackendFactory() != null) {
163 factory = overridingHint.getQueryBackendFactory();
164 }
165 return new QueryEvaluationHint(hints, factory);
166 } else {
167 return new QueryEvaluationHint(hints, overriddenRequirement);
168 }
169 }
170
171 /**
172 * Returns whether the given hint option is overridden.
173 * @since 1.5
174 */
175 public boolean isOptionOverridden(QueryHintOption<?> option) {
176 return getBackendHintSettings().containsKey(option);
177 }
178
179 /**
180 * Returns the value of the given hint option from the given hint collection, or null if not defined.
181 * @since 1.5
182 */
183 @SuppressWarnings("unchecked")
184 public <HintValue> HintValue getValueOrNull(QueryHintOption<HintValue> option) {
185 return (HintValue) getBackendHintSettings().get(option);
186 }
187
188 /**
189 * Returns the value of the given hint option from the given hint collection, or the default value if not defined.
190 * Intended to be called by backends to find out the definitive value that should be considered.
191 * @since 1.5
192 */
193 public <HintValue> HintValue getValueOrDefault(QueryHintOption<HintValue> option) {
194 return option.getValueOrDefault(this);
195 }
196
197 @Override
198 public int hashCode() {
199 return Objects.hash(backendHintSettings, queryBackendFactory, requirement);
200 }
201
202 @Override
203 public boolean equals(Object obj) {
204 if (this == obj)
205 return true;
206 if (obj == null)
207 return false;
208 if (getClass() != obj.getClass())
209 return false;
210 QueryEvaluationHint other = (QueryEvaluationHint) obj;
211 return Objects.equals(backendHintSettings, other.backendHintSettings)
212 &&
213 Objects.equals(queryBackendFactory, other.queryBackendFactory)
214 &&
215 Objects.equals(requirement, other.requirement)
216 ;
217 }
218
219 @Override
220 public String toString() {
221 StringBuilder sb = new StringBuilder();
222
223 if (getQueryBackendFactory() != null)
224 sb.append("backend: ").append(getQueryBackendFactory().getBackendClass().getSimpleName());
225 if (! backendHintSettings.isEmpty()) {
226 sb.append("hints: ");
227 if(backendHintSettings instanceof AbstractMap){
228 sb.append(backendHintSettings.toString());
229 } else {
230 // we have to iterate on the contents
231
232 String joinedHintMap = backendHintSettings.entrySet().stream()
233 .map(setting -> setting.getKey() + "=" + setting.getValue()).collect(Collectors.joining(", "));
234 sb.append('{').append(joinedHintMap).append('}');
235 }
236 }
237
238 final String result = sb.toString();
239 return result.isEmpty() ? "defaults" : result;
240 }
241}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java
new file mode 100644
index 00000000..2c6bb4de
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java
@@ -0,0 +1,122 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import java.util.Map;
12import java.util.Objects;
13
14/**
15 * Each instance of this class corresponds to a given hint option.
16 *
17 * It is recommended to expose options to clients (and query backends) as public static fields.
18 *
19 * @author Gabor Bergmann
20 * @since 1.5
21 */
22public class QueryHintOption<HintValue> {
23
24 private String optionQualifiedName;
25 private HintValue defaultValue;
26
27 /**
28 * Instantiates an option object with the given name and default value.
29 */
30 public QueryHintOption(String optionQualifiedName, HintValue defaultValue) {
31 this.optionQualifiedName = optionQualifiedName;
32 this.defaultValue = defaultValue;
33 }
34
35 /**
36 * This is the recommended constructor for hint options defined as static fields within an enclosing class.
37 * Combines the qualified name of the hint from the (qualified) name of the enclosing class and a local name (unique within that class).
38 */
39 public <T extends HintValue> QueryHintOption(Class<?> optionNamespace, String optionLocalName, T defaultValue) {
40 this(String.format("%s@%s", optionLocalName, optionNamespace.getName()), defaultValue);
41 }
42
43 /**
44 * Returns the qualified name, a unique string identifier of the option.
45 */
46 public String getQualifiedName() {
47 return optionQualifiedName;
48 }
49
50 /**
51 * Returns the default value of this hint option, which is to be used by query backends in the case no overriding value is assigned.
52 */
53 public HintValue getDefaultValue() {
54 return defaultValue;
55 }
56
57 /**
58 * Returns the value of this hint option from the given hint collection, or the default value if not defined.
59 * Intended to be called by backends to find out the definitive value that should be considered.
60 */
61 @SuppressWarnings("unchecked")
62 public HintValue getValueOrDefault(QueryEvaluationHint hints) {
63 Object value = hints.getValueOrNull(this);
64 if (value == null)
65 return getDefaultValue();
66 else {
67 return (HintValue) value;
68 }
69 }
70
71
72 /**
73 * Returns the value of this hint option from the given hint collection, or null if not defined.
74 */
75 public HintValue getValueOrNull(QueryEvaluationHint hints) {
76 return hints.getValueOrNull(this);
77 }
78
79 /**
80 * Returns whether this hint option is defined in the given hint collection.
81 */
82 public boolean isOverriddenIn(QueryEvaluationHint hints) {
83 return hints.isOptionOverridden(this);
84 }
85
86 /**
87 * Puts a value of this hint option into an option-to-value map.
88 *
89 * <p> This method is offered in lieu of a builder API.
90 * Use this method on any number of hint options in order to populate an option-value map.
91 * Then instantiate the immutable {@link QueryEvaluationHint} using the map.
92 *
93 * @see #insertValueIfNondefault(Map, Object)
94 * @return the hint value that was previously present in the map under this hint option, carrying over the semantics of {@link Map#put(Object, Object)}.
95 */
96 @SuppressWarnings("unchecked")
97 public HintValue insertOverridingValue(Map<QueryHintOption<?>, Object> hints, HintValue overridingValue) {
98 return (HintValue) hints.put(this, overridingValue);
99 }
100
101 /**
102 * Puts a value of this hint option into an option-to-value map, if the given value differs from the default value of the option.
103 * If the default value is provided instead, then the map is not updated.
104 *
105 * <p> This method is offered in lieu of a builder API.
106 * Use this method on any number of hint options in order to populate an option-value map.
107 * Then instantiate the immutable {@link QueryEvaluationHint} using the map.
108 *
109 * @see #insertOverridingValue(Map, Object)
110 * @since 2.0
111 */
112 public void insertValueIfNondefault(Map<QueryHintOption<?>, Object> hints, HintValue overridingValue) {
113 if (!Objects.equals(defaultValue, overridingValue))
114 hints.put(this, overridingValue);
115 }
116
117 @Override
118 public String toString() {
119 return optionQualifiedName;
120 }
121
122}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java
new file mode 100644
index 00000000..6ec6d53e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.backend;
10
11import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess;
12import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
13import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
14
15/**
16 * Uniform way of requesting result providers for pattern calls within queries.
17 * Intended users are query backends, for calling other backends to deliver results of dependee queries.
18 *
19 * @author Gabor Bergmann
20 * @since 2.1
21 */
22public class ResultProviderRequestor {
23 IQueryBackend callerBackend;
24 IQueryResultProviderAccess resultProviderAccess;
25 IQueryBackendHintProvider hintProvider;
26 ICallDelegationStrategy delegationStrategy;
27 QueryEvaluationHint callerHint;
28 QueryEvaluationHint universalOverride;
29
30
31 /**
32 * @param callerBackend the actual backend evaluating the calling pattern.
33 * @param resultProviderAccess
34 * @param hintProvider
35 * @param delegationStrategy
36 * @param callerHint a hint under which the calling pattern is evaluated,
37 * @param universalOverride if non-null, overrides the hint with extra options <i>after</i> the {@link ICallDelegationStrategy}
38 */
39 public ResultProviderRequestor(IQueryBackend callerBackend, IQueryResultProviderAccess resultProviderAccess,
40 IQueryBackendHintProvider hintProvider, ICallDelegationStrategy delegationStrategy,
41 QueryEvaluationHint callerHint, QueryEvaluationHint universalOverride) {
42 super();
43 this.callerBackend = callerBackend;
44 this.resultProviderAccess = resultProviderAccess;
45 this.hintProvider = hintProvider;
46 this.delegationStrategy = delegationStrategy;
47 this.callerHint = callerHint;
48 this.universalOverride = universalOverride;
49 }
50
51
52
53
54 /**
55 *
56 * @param call a {@link PConstraint} in a query that calls another query.
57 * @param spotOverride if non-null, overrides the hint with extra options <i>after</i> the {@link ICallDelegationStrategy}
58 * and the universal override specified in the constructor
59 * @return the obtained result provider
60 */
61 public IQueryResultProvider requestResultProvider(IQueryReference call, QueryEvaluationHint spotOverride) {
62 QueryEvaluationHint hints =
63 delegationStrategy.transformHints(call, callerHint, callerBackend, hintProvider);
64
65 if (universalOverride != null)
66 hints = hints.overrideBy(universalOverride);
67
68 if (spotOverride != null)
69 hints = hints.overrideBy(spotOverride);
70
71 return resultProviderAccess.getResultProvider(call.getReferredQuery(), hints);
72 }
73
74}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java
new file mode 100644
index 00000000..99611758
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java
@@ -0,0 +1,69 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Comparator;
14import java.util.HashMap;
15import java.util.Map;
16import java.util.Set;
17
18/**
19 * Common abstract class for implementers of {@link IQueryMetaContext}
20 *
21 * @author Grill Balázs
22 * @since 1.3
23 *
24 */
25public abstract class AbstractQueryMetaContext implements IQueryMetaContext {
26
27 /**
28 * @since 2.0
29 */
30 @Override
31 public Map<InputKeyImplication, Set<InputKeyImplication>> getConditionalImplications(IInputKey implyingKey) {
32 return new HashMap<>();
33 }
34
35 /**
36 * @since 1.6
37 */
38 @Override
39 public boolean canLeadOutOfScope(IInputKey key) {
40 return key.getArity() > 1;
41 }
42
43 /**
44 * @since 1.6
45 */
46 @Override
47 public Comparator<IInputKey> getSuggestedEliminationOrdering() {
48 return (o1, o2) -> 0;
49 }
50
51 /**
52 * @since 1.6
53 */
54 @Override
55 public Collection<InputKeyImplication> getWeakenedAlternatives(IInputKey implyingKey) {
56 return Collections.emptySet();
57 }
58
59 @Override
60 public boolean isPosetKey(IInputKey key) {
61 return false;
62 }
63
64 @Override
65 public IPosetComparator getPosetComparator(Iterable<IInputKey> key) {
66 return null;
67 }
68
69}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java
new file mode 100644
index 00000000..c797eff9
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11/**
12 * This class is intended to be extended by implementors. The main purpose of this abstract implementation to protect
13 * implementors from future changes in the interface.
14 *
15 * @author Grill Balázs
16 * @since 1.4
17 *
18 */
19public abstract class AbstractQueryRuntimeContext implements IQueryRuntimeContext {
20
21}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java
new file mode 100644
index 00000000..4dbcca88
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11/**
12 * An input key identifies an input (extensional) relation, such as the instance set of a given node or edge type, or the direct containment relation.
13 *
14 * <p> The input key, at the very minimum, is associated with an arity (number of columns), a user-friendly name, and a string identifier (for distributive purposes).
15 *
16 * <p> The input key itself must be an immutable data object that properly overrides equals() and hashCode().
17 * It must be instantiable without using the query context object, so that query specifications may construct the appropriate PQueries.
18 *
19 * @author Bergmann Gabor
20 *
21 */
22public interface IInputKey {
23
24 /**
25 * A user-friendly name that can be shown on screen for debug purposes, included in exceptions, etc.
26 */
27 public String getPrettyPrintableName();
28 /**
29 * An internal string identifier that can be used to uniquely identify to input key (relevant for distributed applications).
30 */
31 public String getStringID();
32
33 /**
34 * The width of tuples in this relation.
35 */
36 public int getArity();
37
38 /**
39 * Returns true iff instance tuples of the key can be enumerated.
40 * <p> If false, the runtime can only test tuple membership in the extensional relation identified by the key, but not enumerate member tuples in general.
41 */
42 boolean isEnumerable();
43
44
45}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java
new file mode 100644
index 00000000..e2d5bcee
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12
13/**
14 * Implementations of this interface aid the query engine with the ordering of poset elements. This information is
15 * particularly important in the delete and re-derive evaluation mode because they let the engine identify monotone
16 * change pairs.
17 *
18 * @author Tamas Szabo
19 * @since 1.6
20 */
21public interface IPosetComparator {
22
23 /**
24 * Returns true if the 'left' tuple of poset elements is smaller or equal than the 'right' tuple of poset elements according to
25 * the partial order that this poset comparator employs.
26 *
27 * @param left
28 * the left tuple of poset elements
29 * @param right
30 * the right tuple of poset elements
31 * @return true if left is smaller or equal to right, false otherwise
32 */
33 public boolean isLessOrEqual(final Tuple left, final Tuple right);
34
35}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java
new file mode 100644
index 00000000..04f00aaa
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import org.apache.log4j.Logger;
12import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
13import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
14import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
15import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17
18/**
19 * This interface is a collector which holds every API that is provided by the engine to control
20 * the operation of the backends.
21 *
22 * @since 1.5
23 * @noimplement This interface is not intended to be implemented by clients.
24 */
25public interface IQueryBackendContext {
26
27 Logger getLogger();
28
29 IQueryRuntimeContext getRuntimeContext();
30
31 IQueryCacheContext getQueryCacheContext();
32
33 IQueryBackendHintProvider getHintProvider();
34
35 IQueryResultProviderAccess getResultProviderAccess();
36
37 QueryAnalyzer getQueryAnalyzer();
38
39 /**
40 * @since 2.0
41 */
42 IMatcherCapability getRequiredMatcherCapability(PQuery query, QueryEvaluationHint overrideHints);
43
44 /**
45 * @since 1.6
46 */
47 boolean areUpdatesDelayed();
48
49}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java
new file mode 100644
index 00000000..617207f6
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
13import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15
16/**
17 * Provides information on already cached queries to query evaluator backends at runtime.
18 *
19 * @author Bergmann Gabor
20 *
21 */
22public interface IQueryCacheContext {
23
24 /**
25 * Checks if there already is a caching result provider for the given query.
26 * <p> Returns false if called while the caching result provider of the given query is being constructed in the first place.
27 */
28 public boolean isResultCached(PQuery query);
29
30 /**
31 * Returns a caching result provider for the given query; it must be constructed if it does not exist yet.
32 * <p> <b>Caution:</b> behavior undefined if called while the caching result provider of the given query is being constructed.
33 * Beware of infinite loops.
34 * <p> <b>Postcondition:</b> {@link IQueryBackend#isCaching()} returns true for the {@link #getQueryBackend()} of the returned provider
35 *
36 * @throws ViatraQueryRuntimeException
37 */
38 public IQueryResultProvider getCachingResultProvider(PQuery query);
39}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java
new file mode 100644
index 00000000..4c22a3cb
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java
@@ -0,0 +1,98 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import java.util.Collection;
12import java.util.Comparator;
13import java.util.Map;
14import java.util.Set;
15
16/**
17 * Provides metamodel information (relationship of input keys) to query evaluator backends at runtime and at query planning time.
18 *
19 * @noimplement Implementors should extend {@link AbstractQueryMetaContext} instead of directly implementing this interface.
20 * @author Bergmann Gabor
21 */
22public interface IQueryMetaContext {
23
24 /**
25 * Returns true iff instance tuples of the given key can be enumerated.
26 * <p> If false, the runtime can only test tuple membership in the extensional relation identified by the key, but not enumerate member tuples in general.
27 * <p> Equivalent to {@link IInputKey#isEnumerable()}.
28 */
29 boolean isEnumerable(IInputKey key);
30
31 /**
32 * Returns true iff the set of instance tuples of the given key is immutable.
33 * <p> If false, the runtime provides notifications upon change.
34 */
35 boolean isStateless(IInputKey key);
36
37 /**
38 * Returns a set of implications (weakened alternatives),
39 * with a suggestion for the query planner that satisfying them first may help in satisfying the implying key.
40 * <p> Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys.
41 * <p> Must follow directly or transitively from implications of {@link #getImplications(IInputKey)}.
42 * @since 1.6
43 */
44 Collection<InputKeyImplication> getWeakenedAlternatives(IInputKey implyingKey);
45
46 /**
47 * Returns known direct implications, e.g. edge supertypes, edge opposites, node type constraints, etc.
48 * <p> Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys.
49 */
50 Collection<InputKeyImplication> getImplications(IInputKey implyingKey);
51
52 /**
53 * Returns known "double dispatch" implications, where the given implying key implies other input keys under certain additional conditions (themselves input keys).
54 * For example, a "type x, unscoped" input key may imply the "type x, in scope" input key under the condition of the input key "x is in scope"
55 *
56 * <p> Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys (either as the implying key or as the additional condition).
57 * <p> Note that symmetry is not required, i.e. the additional conditions do not have to list the same conditional implication.
58 * @return multi-map, where the keys are additional conditions and the values are input key implications jointly implied by the condition and the given implying key.
59 * @since 2.0
60 */
61 Map<InputKeyImplication, Set<InputKeyImplication>> getConditionalImplications(IInputKey implyingKey);
62
63 /**
64 * Returns functional dependencies of the input key expressed in terms of column indices.
65 *
66 * <p> Each entry of the map is a functional dependency rule, where the entry key specifies source columns and the entry value specifies target columns.
67 */
68 Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key);
69
70 /**
71 * For query normalizing, this is the order suggested for trying to eliminate input keys.
72 * @since 1.6
73 */
74 Comparator<IInputKey> getSuggestedEliminationOrdering();
75
76 /**
77 * Tells whether the given {@link IInputKey} is an edge and may lead out of scope.
78 *
79 * @since 1.6
80 */
81 boolean canLeadOutOfScope(IInputKey key);
82
83 /**
84 * Returns true if the given {@link IInputKey} represents a poset type.
85 * @since 1.6
86 */
87 boolean isPosetKey(IInputKey key);
88
89 /**
90 * Returns an {@link IPosetComparator} for the given set of {@link IInputKey}s.
91 *
92 * @param keys an iterable collection of input keys
93 * @return the poset comparator
94 * @since 1.6
95 */
96 IPosetComparator getPosetComparator(Iterable<IInputKey> keys);
97
98}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java
new file mode 100644
index 00000000..7fecd01a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
12import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
13import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
14
15/**
16 * This interface exposes API to request {@link IQueryResultProvider} for {@link PQuery} instances.
17 *
18 * @author Grill Balázs
19 * @since 1.5
20 * @noimplement This interface is not intended to be implemented by clients.
21 */
22public interface IQueryResultProviderAccess {
23
24 /**
25 * Get a result provider for the given {@link PQuery}, which conforms the capabilities requested by the
26 * given {@link QueryEvaluationHint} object.
27 * @throws ViatraQueryRuntimeException
28 */
29 public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints);
30
31}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java
new file mode 100644
index 00000000..61359c1b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java
@@ -0,0 +1,287 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.matchers.context;
11
12import tools.refinery.viatra.runtime.CancellationToken;
13import tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper;
14import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.Accuracy;
18
19import java.lang.reflect.InvocationTargetException;
20import java.util.Optional;
21import java.util.concurrent.Callable;
22
23/**
24 * Provides instance model information (relations corresponding to input keys) to query evaluator backends at runtime.
25 * Implementors shall extend {@link AbstractQueryRuntimeContext} instead directly this interface.
26 *
27 * @author Bergmann Gabor
28 * @noimplement This interface is not intended to be implemented by clients. Extend {@link AbstractQueryRuntimeContext} instead.
29 */
30public interface IQueryRuntimeContext {
31 /**
32 * Provides metamodel-specific info independent of the runtime instance model.
33 */
34 public IQueryMetaContext getMetaContext();
35
36
37 /**
38 * The given callable will be executed, and all model traversals will be delayed until the execution is done. If
39 * there are any outstanding information to be read from the model, a single coalesced model traversal will
40 * initialize the caches and deliver the notifications.
41 *
42 * <p> Calls may be nested. A single coalesced traversal will happen at the end of the outermost call.
43 *
44 * <p> <b>Caution: </b> results returned by the runtime context may be incomplete during the coalescing period, to be corrected by notifications sent during the final coalesced traversal.
45 * For example, if a certain input key is not cached yet, an empty relation may be reported during <code>callable.call()</code>; the cache will be constructed after the call terminates and notifications will deliver the entire content of the relation.
46 * Non-incremental query backends should therefore never enumerate input keys while coalesced (verify using {@link #isCoalescing()}).
47 *
48 * @param callable
49 */
50 public abstract <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException;
51 /**
52 * @return true iff currently within a coalescing section (i.e. within the callable of a call to {@link #coalesceTraversals(Callable)}).
53 */
54 public boolean isCoalescing();
55
56 /**
57 * Returns true if index is available for the given key providing the given service.
58 * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
59 * @since 1.4
60 */
61 public boolean isIndexed(IInputKey key, IndexingService service);
62
63 /**
64 * If the given (enumerable) input key is not yet indexed, the model will be traversed
65 * (after the end of the outermost coalescing block, see {@link IQueryRuntimeContext#coalesceTraversals(Callable)})
66 * so that the index can be built. It is possible that the base indexer will select a higher indexing level merging
67 * multiple indexing requests to an appropriate level.
68 *
69 * <p><b>Postcondition:</b> After invoking this method, {@link #getIndexed(IInputKey, IndexingService)} for the same key
70 * and service will be guaranteed to return the requested or a highing indexing level as soon as {@link #isCoalescing()} first returns false.
71 *
72 * <p><b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
73 * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
74 * @since 1.4
75 */
76 public void ensureIndexed(IInputKey key, IndexingService service);
77
78 /**
79 * Returns the number of tuples in the extensional relation identified by the input key seeded with the given mask and tuple.
80 *
81 * @param key an input key
82 * @param seedMask
83 * a mask that extracts those parameters of the input key (from the entire parameter list) that should be
84 * bound to a fixed value; must not be null. <strong>Note</strong>: any given index must occur at most once in seedMask.
85 * @param seed
86 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
87 * parameterSeedMask, so that for each considered match tuple,
88 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null.
89 *
90 * @return the number of tuples in the model for the given key and seed
91 *
92 * <p><b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
93 * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
94 * @since 1.7
95 */
96 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed);
97
98
99 /**
100 * Gives an estimate of the number of different groups the tuples of the given relation are projected into by the given mask
101 * (e.g. for an identity mask, this means the full relation size). The estimate must meet the required accuracy.
102 *
103 * <p> Must accept any input key, even non-enumerables or those not recognized by this runtime context.
104 * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} is returned.
105 *
106 * <p> PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask.
107 *
108 * @return if available, an estimate of the cardinality of the projection of the given extensional relation, with the desired accuracy.
109 *
110 * @since 2.1
111 */
112 public Optional<Long> estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy);
113
114
115 /**
116 * Gives an estimate of the average size of different groups the tuples of the given relation are projected into by the given mask
117 * (e.g. for an identity mask, this means 1, while for an empty mask, the result is the full relation size).
118 * The estimate must meet the required accuracy.
119 *
120 * <p> Must accept any input key, even non-enumerables or those not recognized by this runtime context.
121 * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned.
122 *
123 * <p> For an empty relation, zero is acceptable as an exact answer.
124 *
125 * <p> PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask.
126 *
127 * @return if available, an estimate of the average size of each projection group of the given extensional relation, with the desired accuracy.
128 *
129 * @since 2.1
130 */
131 public default Optional<Double> estimateAverageBucketSize(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) {
132 if (key.isEnumerable()) {
133 return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy,
134 (mask, accuracy) -> this.estimateCardinality(key, mask, accuracy));
135 } else return groupMask.isIdentity() ? Optional.of(1.0) : Optional.empty();
136 }
137
138
139 /**
140 * Returns the tuples in the extensional relation identified by the input key, optionally seeded with the given tuple.
141 *
142 * @param key an input key
143 * @param seedMask
144 * a mask that extracts those parameters of the input key (from the entire parameter list) that should be
145 * bound to a fixed value; must not be null. <strong>Note</strong>: any given index must occur at most once in seedMask.
146 * @param seed
147 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
148 * parameterSeedMask, so that for each considered match tuple,
149 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null.
150 * @return the tuples in the model for the given key and seed
151 *
152 * <p><b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
153 * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
154 * @since 1.7
155 */
156 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed);
157
158 /**
159 * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples
160 * are bound by the seed except for one.
161 *
162 * <p>
163 * Selects the tuples in the extensional relation identified by the input key, optionally seeded with the given
164 * tuple, and then returns the single value from each tuple which is not bound by the ssed mask.
165 *
166 * @param key
167 * an input key
168 * @param seedMask
169 * a mask that extracts those parameters of the input key (from the entire parameter list) that should be
170 * bound to a fixed value; must not be null. <strong>Note</strong>: any given index must occur at most
171 * once in seedMask, and seedMask must include all parameters in any arbitrary order except one.
172 * @param seed
173 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
174 * parameterSeedMask, so that for each considered match tuple,
175 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null.
176 * @return the objects in the model for the given key and seed
177 *
178 * <p>
179 * <b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
180 * @throws IllegalArgumentException
181 * if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
182 * @since 1.7
183 */
184 public Iterable<? extends Object> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed);
185
186 /**
187 * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples
188 * are bound by the seed.
189 *
190 * <p>
191 * Returns whether the given tuple is in the extensional relation identified by the input key.
192 *
193 * <p>
194 * Note: this call works for non-enumerable input keys as well.
195 *
196 * @param key
197 * an input key
198 * @param seed
199 * the tuple of fixed values restricting the match set to be considered, in the same order as given in
200 * parameterSeedMask, so that for each considered match tuple,
201 * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null.
202 * @return true iff there is at least a single tuple contained in the relation that corresponds to the seed tuple
203 * @since 2.0
204 */
205 public boolean containsTuple(IInputKey key, ITuple seed);
206
207
208 /**
209 * Subscribes for updates in the extensional relation identified by the input key, optionally seeded with the given tuple.
210 * <p> This should be called after invoking
211 *
212 * @param key an input key
213 * @param seed can be null or a tuple with matching arity;
214 * if non-null, only those updates in the model are notified about
215 * that match the seed at positions where the seed is non-null.
216 * @param listener will be notified of future changes
217 *
218 * <p><b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
219 * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
220 */
221 public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener);
222
223 /**
224 * Unsubscribes from updates in the extensional relation identified by the input key, optionally seeded with the given tuple.
225 *
226 * @param key an input key
227 * @param seed can be null or a tuple with matching arity;
228 * if non-null, only those updates in the model are notified about
229 * that match the seed at positions where the seed is non-null.
230 * @param listener will no longer be notified of future changes
231 *
232 * <p><b>Precondition:</b> the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
233 * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}.
234 */
235 public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener);
236 /*
237 TODO: uniqueness
238 */
239
240 /**
241 * Wraps the external element into the internal representation that is to be used by the query backend
242 * <p> model element -> internal object.
243 * <p> null must be mapped to null.
244 */
245 public Object wrapElement(Object externalElement);
246
247 /**
248 * Unwraps the internal representation of the element into its original form
249 * <p> internal object -> model element
250 * <p> null must be mapped to null.
251 */
252 public Object unwrapElement(Object internalElement);
253
254 /**
255 * Unwraps the tuple of elements into the internal representation that is to be used by the query backend
256 * <p> model elements -> internal objects
257 * <p> null must be mapped to null.
258 */
259 public Tuple wrapTuple(Tuple externalElements);
260
261 /**
262 * Unwraps the tuple of internal representations of elements into their original forms
263 * <p> internal objects -> model elements
264 * <p> null must be mapped to null.
265 */
266 public Tuple unwrapTuple(Tuple internalElements);
267
268 /**
269 * Starts wildcard indexing for the given service. After this call, no registration is required for this {@link IndexingService}.
270 * a previously set wildcard level cannot be lowered, only extended.
271 * @since 1.4
272 */
273 public void ensureWildcardIndexing(IndexingService service);
274
275 /**
276 * Execute the given runnable after traversal. It is guaranteed that the runnable is executed as soon as
277 * the indexing is finished. The callback is executed only once, then is removed from the callback queue.
278 * @param traversalCallback
279 * @throws InvocationTargetException
280 * @since 1.4
281 */
282 public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException;
283
284 default CancellationToken getCancellationToken() {
285 return CancellationToken.NONE;
286 }
287}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java
new file mode 100644
index 00000000..7be27d56
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12
13/**
14 * Listens for changes in the runtime context.
15 * @author Bergmann Gabor
16 *
17 */
18public interface IQueryRuntimeContextListener {
19
20 /**
21 * The given tuple was inserted into or removed from the input relation indicated by the given key.
22 * @param key the key identifying the input relation that was updated
23 * @param updateTuple the tuple that was inserted or removed
24 * @param isInsertion true if it was an insertion, false otherwise.
25 */
26 public void update(IInputKey key, Tuple updateTuple, boolean isInsertion);
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java
new file mode 100644
index 00000000..8210765d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java
@@ -0,0 +1,36 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11/**
12 * These are the different services which can be provided by an {@link IQueryRuntimeContext} implementation.
13 *
14 * @author Grill Balázs
15 * @since 1.4
16 *
17 */
18public enum IndexingService {
19
20 /**
21 * Cardinality information is available. Makes possible to calculate
22 * unseeded calls of {@link IQueryRuntimeContext#countTuples(IInputKey, tools.refinery.viatra.runtime.matchers.tuple.Tuple)}
23 */
24 STATISTICS,
25
26 /**
27 * The indexer can provide notifications about changes in the model.
28 */
29 NOTIFICATIONS,
30
31 /**
32 * Enables enumeration of instances and reverse-navigation.
33 */
34 INSTANCES
35
36}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java
new file mode 100644
index 00000000..2a403810
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java
@@ -0,0 +1,103 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.List;
14
15/**
16 * Data object representing the implication of an input key, in use cases including edge supertypes, edge opposites, node type constraints, etc.
17 *
18 * <p> Each instance tuple of the <i>implying input key</i> (if given) implies the presence of an instance tuple of the <i>implied input key</i> consisting of elements of the original tuple at given positions.
19 * When the input key is null, it is not an input constraint but some other source that implies input keys.
20 *
21 * <p> The implication is an immutable data object.
22 *
23 * @author Bergmann Gabor
24 *
25 */
26public final class InputKeyImplication {
27 private IInputKey implyingKey;
28 private IInputKey impliedKey;
29 private List<Integer> impliedIndices;
30
31 /**
32 * Optional. Instance tuples of this input key imply an instance tuple of another key.
33 * Sometimes it is not an input key that implies other input keys, so this attribute can be null.
34 */
35 public IInputKey getImplyingKey() {
36 return implyingKey;
37 }
38 /**
39 * An instance tuple of this input key is implied by another key.
40 */
41 public IInputKey getImpliedKey() {
42 return impliedKey;
43 }
44 /**
45 * The implied instance tuple consists of the values in the implying tuple at these indices.
46 */
47 public List<Integer> getImpliedIndices() {
48 return impliedIndices;
49 }
50 /**
51 * @param implyingKey instance tuples of this input key imply an instance tuple of the other key.
52 * @param impliedKey instance tuple of this input key is implied by the other key.
53 * @param implyingIndices the implied instance tuple consists of the values in the implying tuple at these indices.
54 */
55 public InputKeyImplication(IInputKey implyingKey, IInputKey impliedKey,
56 List<Integer> implyingIndices) {
57 super();
58 this.implyingKey = implyingKey;
59 this.impliedKey = impliedKey;
60 this.impliedIndices = Collections.unmodifiableList(new ArrayList<>(implyingIndices));
61 }
62
63 @Override
64 public int hashCode() {
65 final int prime = 31;
66 int result = 1;
67 result = prime * result
68 + ((impliedIndices == null) ? 0 : impliedIndices.hashCode());
69 result = prime * result
70 + ((impliedKey == null) ? 0 : impliedKey.hashCode());
71 result = prime * result
72 + ((implyingKey == null) ? 0 : implyingKey.hashCode());
73 return result;
74 }
75 @Override
76 public boolean equals(Object obj) {
77 if (this == obj)
78 return true;
79 if (obj == null)
80 return false;
81 if (!(obj instanceof InputKeyImplication))
82 return false;
83 InputKeyImplication other = (InputKeyImplication) obj;
84 if (impliedIndices == null) {
85 if (other.impliedIndices != null)
86 return false;
87 } else if (!impliedIndices.equals(other.impliedIndices))
88 return false;
89 if (impliedKey == null) {
90 if (other.impliedKey != null)
91 return false;
92 } else if (!impliedKey.equals(other.impliedKey))
93 return false;
94 if (implyingKey == null) {
95 if (other.implyingKey != null)
96 return false;
97 } else if (!implyingKey.equals(other.implyingKey))
98 return false;
99 return true;
100 }
101
102
103}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java
new file mode 100644
index 00000000..f9b05e5b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context.common;
10
11import tools.refinery.viatra.runtime.matchers.context.IInputKey;
12
13
14/**
15 * An input key that is identified by a single wrapped object and the class of the wrapper.
16 * @author Bergmann Gabor
17 *
18 */
19public abstract class BaseInputKeyWrapper<Wrapped> implements IInputKey {
20 protected Wrapped wrappedKey;
21
22 public BaseInputKeyWrapper(Wrapped wrappedKey) {
23 super();
24 this.wrappedKey = wrappedKey;
25 }
26
27 public Wrapped getWrappedKey() {
28 return wrappedKey;
29 }
30
31
32 @Override
33 public int hashCode() {
34 return ((wrappedKey == null) ? 0 : wrappedKey.hashCode());
35 }
36
37 @Override
38 public boolean equals(Object obj) {
39 if (this == obj)
40 return true;
41 if (obj == null)
42 return false;
43 if (!(this.getClass().equals(obj.getClass())))
44 return false;
45 BaseInputKeyWrapper other = (BaseInputKeyWrapper) obj;
46 if (wrappedKey == null) {
47 if (other.wrappedKey != null)
48 return false;
49 } else if (!wrappedKey.equals(other.wrappedKey))
50 return false;
51 return true;
52 }
53
54
55}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java
new file mode 100644
index 00000000..eb972c2d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java
@@ -0,0 +1,165 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context.common;
10
11
12
13/**
14 * Instance tuples are of form (x), where object x is an instance of the given Java class or its subclasses.
15 * <p> Fine print 1: classes with the same name are considered equivalent.
16 * Can be instantiated with class name, even if the class itself is not loaded yet; but if the class is available, passing it in the constructor is beneficial to avoid classloading troubles.
17 * <p> Fine print 2: primitive types (char, etc.) are transparently treated as their wrapper class (Character, etc.).
18 * <p> Non-enumerable type, can only be checked.
19 * <p> Stateless type (objects can't change their type)
20 * @author Bergmann Gabor
21 *
22*/
23public class JavaTransitiveInstancesKey extends BaseInputKeyWrapper<String> {
24
25 /**
26 * The actual Class whose (transitive) instances this relation contains. Can be null at compile time, if only the name is available.
27 * Can be a primitive.
28 */
29 private Class<?> cachedOriginalInstanceClass;
30
31 /**
32 * Same as {@link #cachedOriginalInstanceClass}, but primitive classes are replaced with their wrapper classes (e.g. int --> java.lang.Integer).
33 */
34 private Class<?> cachedWrapperInstanceClass;
35
36 /**
37 * Preferred constructor.
38 */
39 public JavaTransitiveInstancesKey(Class<?> instanceClass) {
40 this(primitiveTypeToWrapperClass(instanceClass).getName());
41 this.cachedOriginalInstanceClass = instanceClass;
42 }
43
44 /**
45 * Call this constructor only in contexts where the class itself is not available for loading, e.g. it has not yet been compiled.
46 */
47 public JavaTransitiveInstancesKey(String className) {
48 super(className);
49 }
50
51
52 /**
53 * Returns null if class cannot be loaded.
54 */
55 private Class<?> getOriginalInstanceClass() {
56 if (cachedOriginalInstanceClass == null) {
57 try {
58 resolveClassInternal();
59 } catch (ClassNotFoundException e) {
60 // class not yet available at this point
61 }
62 }
63 return cachedOriginalInstanceClass;
64 }
65
66
67 /**
68 * @return non-null instance class
69 * @throws ClassNotFoundException
70 */
71 private Class<?> forceGetOriginalInstanceClass() throws ClassNotFoundException {
72 if (cachedOriginalInstanceClass == null) {
73 resolveClassInternal();
74 }
75 return cachedOriginalInstanceClass;
76 }
77
78 /**
79 * @return non-null instance class, wrapped if primitive class
80 * @throws ClassNotFoundException
81 */
82 public Class<?> forceGetWrapperInstanceClass() throws ClassNotFoundException {
83 forceGetOriginalInstanceClass();
84 return getWrapperInstanceClass();
85 }
86 /**
87 * @return non-null instance class, wrapped if primitive class
88 * @throws ClassNotFoundException
89 */
90 public Class<?> forceGetInstanceClass() throws ClassNotFoundException {
91 return forceGetWrapperInstanceClass();
92 }
93
94 /**
95 * @return instance class, wrapped if primitive class, null if class cannot be loaded
96 */
97 public Class<?> getWrapperInstanceClass() {
98 if (cachedWrapperInstanceClass == null) {
99 cachedWrapperInstanceClass = primitiveTypeToWrapperClass(getOriginalInstanceClass());
100 }
101 return cachedWrapperInstanceClass;
102 }
103 /**
104 * @return instance class, wrapped if primitive class, null if class cannot be loaded
105 */
106 public Class<?> getInstanceClass() {
107 return getWrapperInstanceClass();
108 }
109
110 private void resolveClassInternal() throws ClassNotFoundException {
111 cachedOriginalInstanceClass = Class.forName(wrappedKey);
112 }
113
114 @Override
115 public String getPrettyPrintableName() {
116 getWrapperInstanceClass();
117 return cachedWrapperInstanceClass == null ? wrappedKey == null ? "<null>" : wrappedKey : cachedWrapperInstanceClass.getName();
118 }
119
120 @Override
121 public String getStringID() {
122 return "javaClass#"+ getPrettyPrintableName();
123 }
124
125 @Override
126 public int getArity() {
127 return 1;
128 }
129
130 @Override
131 public boolean isEnumerable() {
132 return false;
133 }
134
135 @Override
136 public String toString() {
137 return this.getPrettyPrintableName();
138 }
139
140 private static Class<?> primitiveTypeToWrapperClass(Class<?> instanceClass) {
141 if (instanceClass != null && instanceClass.isPrimitive()) {
142 if (Void.TYPE.equals(instanceClass))
143 return Void.class;
144 if (Boolean.TYPE.equals(instanceClass))
145 return Boolean.class;
146 if (Character.TYPE.equals(instanceClass))
147 return Character.class;
148 if (Byte.TYPE.equals(instanceClass))
149 return Byte.class;
150 if (Short.TYPE.equals(instanceClass))
151 return Short.class;
152 if (Integer.TYPE.equals(instanceClass))
153 return Integer.class;
154 if (Long.TYPE.equals(instanceClass))
155 return Long.class;
156 if (Float.TYPE.equals(instanceClass))
157 return Float.class;
158 if (Double.TYPE.equals(instanceClass))
159 return Double.class;
160 }
161 return instanceClass;
162 }
163
164
165}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java
new file mode 100644
index 00000000..bb24ec8c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java
@@ -0,0 +1,153 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Abel Hegedus, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.context.surrogate;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Map;
15import java.util.NoSuchElementException;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.context.IInputKey;
19import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
20import tools.refinery.viatra.runtime.matchers.util.IProvider;
21import tools.refinery.viatra.runtime.matchers.util.Preconditions;
22import tools.refinery.viatra.runtime.matchers.util.SingletonInstanceProvider;
23
24/**
25 * @author Abel Hegedus
26 *
27 */
28public class SurrogateQueryRegistry {
29
30 private Map<IInputKey, IProvider<PQuery>> registeredSurrogateQueryMap = new HashMap<>();
31 private Map<IInputKey, IProvider<PQuery>> dynamicSurrogateQueryMap = new HashMap<>();
32
33 /**
34 * Hidden constructor
35 */
36 private SurrogateQueryRegistry() {
37 }
38
39 private static final SurrogateQueryRegistry INSTANCE = new SurrogateQueryRegistry();
40
41 public static SurrogateQueryRegistry instance() {
42 return INSTANCE;
43 }
44
45 /**
46 *
47 * @param feature
48 * @param surrogateQuery
49 * @return the previous surrogate query associated with feature, or null if there was no such query FQN registered
50 * @throws IllegalArgumentException if feature or surrogateQuery is null
51 */
52 public IProvider<PQuery> registerSurrogateQueryForFeature(IInputKey feature, PQuery surrogateQuery) {
53 Preconditions.checkArgument(surrogateQuery != null, "Surrogate query must not be null!");
54 return registerSurrogateQueryForFeature(feature, new SingletonInstanceProvider<PQuery>(surrogateQuery));
55 }
56
57 /**
58 *
59 * @param feature
60 * @param surrogateQuery
61 * @return the previous surrogate query associated with feature, or null
62 * if there was no such query registered
63 * @throws IllegalArgumentException
64 * if feature or surrogateQuery is null
65 */
66 public IProvider<PQuery> registerSurrogateQueryForFeature(IInputKey feature, IProvider<PQuery> surrogateQueryProvider) {
67 Preconditions.checkArgument(feature != null, "Feature must not be null!");
68 Preconditions.checkArgument(surrogateQueryProvider != null, "Surrogate query must not be null!");
69 return registeredSurrogateQueryMap.put(feature, surrogateQueryProvider);
70 }
71
72 public IProvider<PQuery> addDynamicSurrogateQueryForFeature(IInputKey feature, PQuery surrogateQuery) {
73 Preconditions.checkArgument(surrogateQuery != null, "Surrogate query FQN must not be null!");
74 return addDynamicSurrogateQueryForFeature(feature, new SingletonInstanceProvider<PQuery>(surrogateQuery));
75 }
76
77 public IProvider<PQuery> addDynamicSurrogateQueryForFeature(IInputKey feature, IProvider<PQuery> surrogateQuery) {
78 Preconditions.checkArgument(feature != null, "Feature must not be null!");
79 Preconditions.checkArgument(surrogateQuery != null, "Surrogate query FQN must not be null!");
80 return dynamicSurrogateQueryMap.put(feature, surrogateQuery);
81 }
82
83 public IProvider<PQuery> removeDynamicSurrogateQueryForFeature(IInputKey feature) {
84 Preconditions.checkArgument(feature != null, "Feature must not be null!");
85 return dynamicSurrogateQueryMap.remove(feature);
86 }
87
88 /**
89 *
90 * @param feature that may have surrogate query defined, null not allowed
91 * @return true if the feature has a surrogate query defined
92 * @throws IllegalArgumentException if feature is null
93 */
94 public boolean hasSurrogateQueryFQN(IInputKey feature) {
95 Preconditions.checkArgument(feature != null, "Feature must not be null!");
96 boolean surrogateExists = dynamicSurrogateQueryMap.containsKey(feature);
97 if(!surrogateExists){
98 surrogateExists = registeredSurrogateQueryMap.containsKey(feature);
99 }
100 return surrogateExists;
101 }
102
103 /**
104 *
105 * @param feature for which the surrogate query FQN should be returned
106 * @return the surrogate query FQN defined for the feature
107 * @throws IllegalArgumentException if feature is null
108 * @throws NoSuchElementException if the feature has no surrogate query defined, use {@link #hasSurrogateQueryFQN} to check
109 */
110 public PQuery getSurrogateQuery(IInputKey feature) {
111 Preconditions.checkArgument(feature != null, "Feature must not be null!");
112 IProvider<PQuery> surrogate = dynamicSurrogateQueryMap.get(feature);
113 if(surrogate == null) {
114 surrogate = registeredSurrogateQueryMap.get(feature);
115 }
116 if(surrogate != null) {
117 return surrogate.get();
118 } else {
119 throw new NoSuchElementException(String.format("Feature %s has no surrogate query defined! Use #hasSurrogateQueryFQN to check existence.", feature));
120 }
121 }
122
123 /**
124 * @return an unmodifiable set of features with registered surrogate queries
125 */
126 public Set<IInputKey> getRegisteredSurrogateQueries() {
127 return Collections.unmodifiableSet(getRegisteredSurrogateQueriesInternal());
128 }
129
130 private Set<IInputKey> getRegisteredSurrogateQueriesInternal() {
131 return registeredSurrogateQueryMap.keySet();
132 }
133
134 /**
135 * @return an unmodifiable set of features with dynamically added surrogate queries
136 */
137 public Set<IInputKey> getDynamicSurrogateQueries() {
138 return Collections.unmodifiableSet(getDynamicSurrogateQueriesInternal());
139 }
140
141 private Set<IInputKey> getDynamicSurrogateQueriesInternal() {
142 return dynamicSurrogateQueryMap.keySet();
143 }
144
145 /**
146 * @return an unmodifiable set that contains all features with surrogate queries.
147 */
148 public Set<IInputKey> getAllSurrogateQueries() {
149 Set<IInputKey> results = new HashSet<>(getRegisteredSurrogateQueriesInternal());
150 results.addAll(getDynamicSurrogateQueriesInternal());
151 return results;
152 }
153}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java
new file mode 100644
index 00000000..66587f77
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java
@@ -0,0 +1,58 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories;
10
11import java.util.Iterator;
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
19import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
20import tools.refinery.viatra.runtime.matchers.util.IMemory;
21
22/**
23 * Common parts of nullary and identity specializations.
24 *
25 * @noextend This class is not intended to be subclassed by clients.
26 * @author Gabor Bergmann
27 * @since 2.0
28 */
29abstract class AbstractTrivialMaskedMemory<Timestamp extends Comparable<Timestamp>> extends MaskedTupleMemory<Timestamp> {
30
31 protected IMemory<Tuple> tuples;
32
33 protected AbstractTrivialMaskedMemory(TupleMask mask, MemoryType bucketType, Object owner) {
34 super(mask, owner);
35 tuples = CollectionsFactory.createMemory(Object.class, bucketType);
36 }
37
38 @Override
39 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(ITuple signature) {
40 throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!");
41 }
42
43 @Override
44 public void clear() {
45 tuples.clear();
46 }
47
48 @Override
49 public int getTotalSize() {
50 return tuples.size();
51 }
52
53 @Override
54 public Iterator<Tuple> iterator() {
55 return tuples.iterator();
56 }
57
58}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java
new file mode 100644
index 00000000..92081409
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.memories;
11
12import java.util.Collection;
13import java.util.Iterator;
14import java.util.Map;
15
16import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
21import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
22import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
23import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
25
26/**
27 * @author Gabor Bergmann
28 *
29 * Default implementation that covers all cases.
30 *
31 * @since 2.0
32 */
33public final class DefaultMaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
34 extends MaskedTupleMemory<Timestamp> {
35 /**
36 * Maps a signature tuple to the bucket of tuples with the given signature.
37 *
38 * @since 2.0
39 */
40 protected IMultiLookup<Tuple, Tuple> signatureToTuples;
41
42 /**
43 * @param mask
44 * The mask used to index the matchings
45 * @param owner
46 * the object "owning" this memory
47 * @param bucketType
48 * the kind of tuple collection maintained for each indexer bucket
49 * @since 2.0
50 */
51 public DefaultMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) {
52 super(mask, owner);
53 signatureToTuples = CollectionsFactory.<Tuple, Tuple> createMultiLookup(Object.class, bucketType, Object.class);
54 }
55
56 @Override
57 public boolean add(Tuple tuple) {
58 Tuple signature = mask.transform(tuple);
59 return add(tuple, signature);
60 }
61
62 @Override
63 public boolean add(Tuple tuple, Tuple signature) {
64 try {
65 return signatureToTuples.addPair(signature, tuple) == ChangeGranularity.KEY;
66 } catch (IllegalStateException ex) { // ignore worthless internal exception details
67 throw raiseDuplicateInsertion(tuple);
68 }
69
70 }
71
72 @Override
73 public boolean remove(Tuple tuple) {
74 Tuple signature = mask.transform(tuple);
75 return remove(tuple, signature);
76 }
77
78 @Override
79 public boolean remove(Tuple tuple, Tuple signature) {
80 try {
81 return signatureToTuples.removePair(signature, tuple) == ChangeGranularity.KEY;
82 } catch (IllegalStateException ex) { // ignore worthless internal exception details
83 throw raiseDuplicateDeletion(tuple);
84 }
85 }
86
87 @Override
88 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(ITuple signature) {
89 throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!");
90 }
91
92 @Override
93 public Collection<Tuple> get(ITuple signature) {
94 IMemoryView<Tuple> bucket = signatureToTuples.lookupUnsafe(signature);
95 return bucket == null ? null : bucket.distinctValues();
96 }
97
98 @Override
99 public void clear() {
100 signatureToTuples.clear();
101 }
102
103 @Override
104 public Iterable<Tuple> getSignatures() {
105 return signatureToTuples.distinctKeys();
106 }
107
108 @Override
109 public Iterator<Tuple> iterator() {
110 return signatureToTuples.distinctValues().iterator();
111 }
112
113 @Override
114 public int getTotalSize() {
115 int i = 0;
116 for (Tuple key : signatureToTuples.distinctKeys()) {
117 i += signatureToTuples.lookup(key).size();
118 }
119 return i;
120 }
121
122 @Override
123 public int getKeysetSize() {
124 return signatureToTuples.countKeys();
125 }
126
127}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java
new file mode 100644
index 00000000..dc59daf5
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java
@@ -0,0 +1,77 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories;
10
11import java.util.Collection;
12import java.util.Collections;
13
14import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
15import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
16import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
17import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
18
19/**
20 * Specialized for identity mask; tuples are stored as a simple set/multiset memory.
21 *
22 * @author Gabor Bergmann
23 * @since 2.0
24 */
25public final class IdentityMaskedTupleMemory<Timestamp extends Comparable<Timestamp>> extends AbstractTrivialMaskedMemory<Timestamp> {
26
27 /**
28 * @param mask
29 * The mask used to index the matchings
30 * @param owner the object "owning" this memory
31 * @param bucketType the kind of tuple collection maintained for each indexer bucket
32 * @since 2.0
33 */
34 public IdentityMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) {
35 super(mask, bucketType, owner);
36 if (!mask.isIdentity()) throw new IllegalArgumentException(mask.toString());
37 }
38
39 @Override
40 public int getKeysetSize() {
41 return tuples.size();
42 }
43
44 @Override
45 public Iterable<Tuple> getSignatures() {
46 return tuples;
47 }
48
49 @Override
50 public Collection<Tuple> get(ITuple signature) {
51 Tuple contained = tuples.theContainedVersionOfUnsafe(signature);
52 return contained != null ?
53 Collections.singleton(contained) :
54 null;
55 }
56
57 @Override
58 public boolean remove(Tuple tuple, Tuple signature) {
59 return tuples.removeOne(tuple);
60 }
61
62 @Override
63 public boolean remove(Tuple tuple) {
64 return tuples.removeOne(tuple);
65 }
66
67 @Override
68 public boolean add(Tuple tuple, Tuple signature) {
69 return tuples.addOne(tuple);
70 }
71
72 @Override
73 public boolean add(Tuple tuple) {
74 return tuples.addOne(tuple);
75 }
76
77}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java
new file mode 100644
index 00000000..62377624
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java
@@ -0,0 +1,385 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.Map;
15
16import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyDefaultMaskedTupleMemory;
17import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyIdentityMaskedTupleMemory;
18import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyNullaryMaskedTupleMemory;
19import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyUnaryMaskedTupleMemory;
20import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
22import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
23import tools.refinery.viatra.runtime.matchers.util.Clearable;
24import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
25import tools.refinery.viatra.runtime.matchers.util.resumable.MaskedResumable;
26import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
27import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
28
29/**
30 * Indexes a collection of tuples by their signature (i.e. footprint, projection) obtained according to a mask. May
31 * belong to an "owner" (for documentation / traceability purposes).
32 * <p>
33 * There are timeless and timely versions of the different memories. Timely versions associate {@link Timeline}s with
34 * the stored tuples.
35 *
36 * @noextend This class is not intended to be subclassed by clients.
37 * @author Gabor Bergmann
38 * @author Tamas Szabo
39 * @since 2.0
40 */
41public abstract class MaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
42 implements Clearable, MaskedResumable<Timestamp> {
43
44 /**
45 * Creates a new memory for the given owner that indexes tuples according to the given mask.
46 */
47 public static <T extends Comparable<T>> MaskedTupleMemory<T> create(final TupleMask mask,
48 final MemoryType bucketType, final Object owner) {
49 return create(mask, bucketType, owner, false);
50 }
51
52 /**
53 * Creates a new memory for the given owner that indexes tuples according to the given mask. Clients can specify if
54 * the created memory should be timely or not. <br>
55 * <br>
56 * Timely means that tuples are associated with a timeline.
57 *
58 * @since 2.3
59 */
60 public static <T extends Comparable<T>> MaskedTupleMemory<T> create(final TupleMask mask,
61 final MemoryType bucketType, final Object owner, final boolean isTimely) {
62 return create(mask, bucketType, owner, isTimely, false);
63 }
64
65 /**
66 * Creates a new memory for the given owner that indexes tuples according to the given mask. Clients can specify if
67 * the created memory should be timely or not. In case of timely memory, clients can also specify if the memory is
68 * lazy or not. <br>
69 * <br>
70 * Timely means that tuples are associated with a timeline. <br>
71 * <br>
72 * Lazyness can only be used together with timely memories. It means that the maintenance of the timelines is lazy,
73 * that is, the memory only updates its internal data structures at the timestamp affected by an update, and can be
74 * instructed later to resume the maintenance at higher timestamps, as well.
75 *
76 * @since 2.4
77 */
78 public static <T extends Comparable<T>> MaskedTupleMemory<T> create(final TupleMask mask,
79 final MemoryType bucketType, final Object owner, final boolean isTimely, final boolean isLazy) {
80 if (isTimely) {
81 if (bucketType != MemoryType.SETS) {
82 throw new IllegalArgumentException("Timely memories only support SETS as the bucket type!");
83 }
84 if (mask.isIdentity()) {
85 return new TimelyIdentityMaskedTupleMemory<T>(mask, owner, isLazy);
86 } else if (0 == mask.getSize()) {
87 return new TimelyNullaryMaskedTupleMemory<T>(mask, owner, isLazy);
88 } else if (1 == mask.getSize()) {
89 return new TimelyUnaryMaskedTupleMemory<T>(mask, owner, isLazy);
90 } else {
91 return new TimelyDefaultMaskedTupleMemory<T>(mask, owner, isLazy);
92 }
93 } else {
94 if (isLazy) {
95 throw new IllegalArgumentException("Lazy maintenance is only supported by timely memories!");
96 }
97 if (mask.isIdentity()) {
98 return new IdentityMaskedTupleMemory<T>(mask, bucketType, owner);
99 } else if (0 == mask.getSize()) {
100 return new NullaryMaskedTupleMemory<T>(mask, bucketType, owner);
101 } else if (1 == mask.getSize()) {
102 return new UnaryMaskedTupleMemory<T>(mask, bucketType, owner);
103 } else {
104 return new DefaultMaskedTupleMemory<T>(mask, bucketType, owner);
105 }
106 }
107 }
108
109 @Override
110 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp) {
111 throw new UnsupportedOperationException("This is only supported by lazy timely memory implementations!");
112 }
113
114 @Override
115 public Iterable<Tuple> getResumableSignatures() {
116 throw new UnsupportedOperationException("This is only supported by lazy timely memory implementations!");
117 }
118
119 @Override
120 public Timestamp getResumableTimestamp() {
121 return null;
122 }
123
124 /**
125 * Initializes the contents of this memory based on the contents of another memory. The default value is associated
126 * with each tuple in the timely memories.
127 *
128 * @since 2.3
129 */
130 public void initializeWith(final MaskedTupleMemory<Timestamp> other, final Timestamp defaultValue) {
131 throw new UnsupportedOperationException("This is only supported by timely memory implementations!");
132 }
133
134 /**
135 * Returns true if there is any tuple with the given signature that is present at the timestamp +inf, false
136 * otherwise.
137 * @since 2.4
138 */
139 public boolean isPresentAtInfinity(final ITuple signature) {
140 return get(signature) != null;
141 }
142
143 /**
144 * Returns true of this memory is timely, false otherwise.
145 *
146 * @since 2.3
147 */
148 public boolean isTimely() {
149 return false;
150 }
151
152 /**
153 * The mask by which the tuples are indexed.
154 */
155 protected final TupleMask mask;
156
157 /**
158 * The object "owning" this memory. May be null.
159 *
160 * @since 1.7
161 */
162 protected final Object owner;
163
164 /**
165 * The node owning this memory. May be null.
166 *
167 * @since 2.0
168 */
169 public Object getOwner() {
170 return owner;
171 }
172
173 /**
174 * The mask according to which tuples are projected and indexed.
175 *
176 * @since 2.0
177 */
178 public TupleMask getMask() {
179 return mask;
180 }
181
182 /**
183 * @return the number of distinct signatures of all stored tuples.
184 */
185 public abstract int getKeysetSize();
186
187 /**
188 * @return the total number of distinct tuples stored. Multiple copies of the same tuple, if allowed, are counted as
189 * one.
190 *
191 * <p>
192 * This is currently not cached but computed on demand. It is therefore not efficient, and shall only be
193 * used for debug / profiling purposes.
194 */
195 public abstract int getTotalSize();
196
197 /**
198 * Iterates over distinct tuples stored in the memory, regardless of their signatures.
199 */
200 public abstract Iterator<Tuple> iterator();
201
202 /**
203 * Retrieves a read-only view of exactly those signatures for which at least one tuple is stored
204 *
205 * @since 2.0
206 */
207 public abstract Iterable<Tuple> getSignatures();
208
209 /**
210 * Retrieves tuples that have the specified signature
211 *
212 * @return collection of tuples found, null if none
213 */
214 public abstract Collection<Tuple> get(final ITuple signature);
215
216 /**
217 * Retrieves the tuples and their associated timelines that have the specified signature.
218 *
219 * @return the mappings from tuples to timelines, null if there is no mapping for the signature
220 * @since 2.4
221 */
222 public abstract Map<Tuple, Timeline<Timestamp>> getWithTimeline(final ITuple signature);
223
224 /**
225 * Retrieves tuples that have the specified signature.
226 *
227 * @return collection of tuples found, never null
228 * @since 2.1
229 */
230 public Collection<Tuple> getOrEmpty(final ITuple signature) {
231 final Collection<Tuple> result = get(signature);
232 return result == null ? Collections.emptySet() : result;
233 }
234
235 /**
236 * Retrieves tuples with their associated timelines that have the specified signature.
237 *
238 * @return map of tuples and timelines found, never null
239 * @since 2.4
240 */
241 public Map<Tuple, Timeline<Timestamp>> getOrEmptyWithTimeline(final ITuple signature) {
242 final Map<Tuple, Timeline<Timestamp>> result = getWithTimeline(signature);
243 return result == null ? Collections.emptyMap() : result;
244 }
245
246 /**
247 * Removes a tuple occurrence from the memory with the given signature.
248 *
249 * @param tuple
250 * the tuple to be removed from the memory
251 * @param signature
252 * precomputed footprint of the tuple according to the mask
253 *
254 * @return true if this was the the last occurrence of the signature (according to the mask)
255 */
256 public boolean remove(final Tuple tuple, final Tuple signature) {
257 throw new UnsupportedOperationException("This is only supported by timeless memory implementations!");
258 }
259
260 /**
261 * Removes a tuple occurrence from the memory with the given signature and timestamp.
262 *
263 * @param tuple
264 * the tuple to be removed from the memory
265 * @param signature
266 * precomputed footprint of the tuple according to the mask
267 * @param timestamp
268 * the timestamp associated with the tuple
269 *
270 * @return A {@link Diff} describing how the timeline of the given tuple changed.
271 *
272 * @since 2.4
273 */
274 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
275 throw new UnsupportedOperationException("This is only supported by timely memory implementations!");
276 }
277
278 /**
279 * Removes a tuple occurrence from the memory.
280 *
281 * @param tuple
282 * the tuple to be removed from the memory
283 *
284 * @return true if this was the the last occurrence of the signature (according to the mask)
285 */
286 public boolean remove(final Tuple tuple) {
287 throw new UnsupportedOperationException("This is only supported by timeless memory implementations!");
288 }
289
290 /**
291 * Removes a tuple occurrence from the memory with the given timestamp.
292 *
293 * @param tuple
294 * the tuple to be removed from the memory
295 * @param timestamp
296 * the timestamp associated with the tuple
297 *
298 * @return A {@link Diff} describing how the timeline of the given tuple changed.
299 *
300 * @since 2.4
301 */
302 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
303 throw new UnsupportedOperationException("This is only supported by timely memory implementations!");
304 }
305
306 /**
307 * Adds a tuple occurrence to the memory with the given signature.
308 *
309 * @param tuple
310 * the tuple to be added to the memory
311 * @param signature
312 * precomputed footprint of the tuple according to the mask
313 *
314 * @return true if new signature encountered (according to the mask)
315 */
316 public boolean add(final Tuple tuple, final Tuple signature) {
317 throw new UnsupportedOperationException("This is only supported by timeless memory implementations!");
318 }
319
320 /**
321 * Adds a tuple occurrence to the memory with the given signature and timestamp.
322 *
323 * @param tuple
324 * the tuple to be added to the memory
325 * @param signature
326 * precomputed footprint of the tuple according to the mask
327 * @param timestamp
328 * the timestamp associated with the tuple
329 *
330 * @return A {@link Diff} describing how the timeline of the given tuple changed.
331 *
332 * @since 2.4
333 */
334 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
335 throw new UnsupportedOperationException("This is only supported by timely memory implementations!");
336 }
337
338 /**
339 * Adds a tuple occurrence to the memory.
340 *
341 * @param tuple
342 * the tuple to be added to the memory
343 *
344 * @return true if new signature encountered (according to the mask)
345 */
346 public boolean add(final Tuple tuple) {
347 throw new UnsupportedOperationException("This is only supported by timeless memory implementations!");
348 }
349
350 /**
351 * Adds a tuple occurrence to the memory with the given timestamp.
352 *
353 * @param tuple
354 * the tuple to be added to the memory
355 * @param timestamp
356 * the timestamp associated with the tuple
357 *
358 * @return A {@link Diff} describing how the timeline of the given tuple changed.
359 *
360 * @since 2.4
361 */
362 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
363 throw new UnsupportedOperationException("This is only supported by timely memory implementations!");
364 }
365
366 protected MaskedTupleMemory(final TupleMask mask, final Object owner) {
367 super();
368 this.mask = mask;
369 this.owner = owner;
370 }
371
372 protected IllegalStateException raiseDuplicateInsertion(final Tuple tuple) {
373 return new IllegalStateException(String.format("Duplicate insertion of tuple %s into %s", tuple, owner));
374 }
375
376 protected IllegalStateException raiseDuplicateDeletion(final Tuple tuple) {
377 return new IllegalStateException(String.format("Duplicate deletion of tuple %s from %s", tuple, owner));
378 }
379
380 @Override
381 public String toString() {
382 return getClass().getSimpleName() + "<" + mask + ">@" + owner;
383 }
384
385} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java
new file mode 100644
index 00000000..7fa9e053
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
20
21/**
22 * Specialized for nullary mask; tuples are stored as a simple set/multiset memory.
23 *
24 * @author Gabor Bergmann
25 * @since 2.0
26 */
27public final class NullaryMaskedTupleMemory<Timestamp extends Comparable<Timestamp>> extends AbstractTrivialMaskedMemory<Timestamp> {
28
29 protected static final Set<Tuple> UNIT_RELATION =
30 Collections.singleton(Tuples.staticArityFlatTupleOf());
31 protected static final Set<Tuple> EMPTY_RELATION =
32 Collections.emptySet();
33 /**
34 * @param mask
35 * The mask used to index the matchings
36 * @param owner the object "owning" this memory
37 * @param bucketType the kind of tuple collection maintained for each indexer bucket
38 * @since 2.0
39 */
40 public NullaryMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) {
41 super(mask, bucketType, owner);
42 if (0 != mask.getSize()) throw new IllegalArgumentException(mask.toString());
43 }
44
45 @Override
46 public int getKeysetSize() {
47 return tuples.isEmpty() ? 0 : 1;
48 }
49
50 @Override
51 public Iterable<Tuple> getSignatures() {
52 return tuples.isEmpty() ? EMPTY_RELATION : UNIT_RELATION;
53 }
54
55 @Override
56 public Collection<Tuple> get(ITuple signature) {
57 if (0 == signature.getSize())
58 return tuples.distinctValues();
59 else return null;
60 }
61
62 @Override
63 public boolean remove(Tuple tuple, Tuple signature) {
64 tuples.removeOne(tuple);
65 return tuples.isEmpty();
66 }
67
68 @Override
69 public boolean remove(Tuple tuple) {
70 return remove(tuple, null);
71 }
72
73 @Override
74 public boolean add(Tuple tuple, Tuple signature) {
75 boolean wasEmpty = tuples.isEmpty();
76 tuples.addOne(tuple);
77 return wasEmpty;
78 }
79
80 @Override
81 public boolean add(Tuple tuple) {
82 return add(tuple, null);
83 }
84
85}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java
new file mode 100644
index 00000000..f34cc9e3
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java
@@ -0,0 +1,143 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories;
10
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
19import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
21import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
22import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
23import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
25
26/**
27 * Specialized for unary mask; tuples are indexed by a single column as opposed to a projection (signature) tuple.
28 *
29 * @author Gabor Bergmann
30 * @since 2.0
31 */
32public final class UnaryMaskedTupleMemory<Timestamp extends Comparable<Timestamp>> extends MaskedTupleMemory<Timestamp> {
33
34 protected IMultiLookup<Object, Tuple> columnToTuples;
35 protected final int keyPosition;
36
37 /**
38 * @param mask
39 * The mask used to index the matchings
40 * @param owner the object "owning" this memory
41 * @param bucketType the kind of tuple collection maintained for each indexer bucket
42 * @since 2.0
43 */
44 public UnaryMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) {
45 super(mask, owner);
46 if (1 != mask.getSize()) throw new IllegalArgumentException(mask.toString());
47
48 columnToTuples = CollectionsFactory.<Object, Tuple>createMultiLookup(
49 Object.class, bucketType, Object.class);
50 keyPosition = mask.indices[0];
51 }
52
53 @Override
54 public void clear() {
55 columnToTuples.clear();
56 }
57
58 @Override
59 public int getKeysetSize() {
60 return columnToTuples.countKeys();
61 }
62
63 @Override
64 public int getTotalSize() {
65 int i = 0;
66 for (Object key : columnToTuples.distinctKeys()) {
67 i += columnToTuples.lookup(key).size();
68 }
69 return i;
70 }
71
72 @Override
73 public Iterator<Tuple> iterator() {
74 return columnToTuples.distinctValues().iterator();
75 }
76
77 @Override
78 public Iterable<Tuple> getSignatures() {
79 return () -> {
80 Iterator<Object> wrapped = columnToTuples.distinctKeys().iterator();
81 return new Iterator<Tuple>() {
82 @Override
83 public boolean hasNext() {
84 return wrapped.hasNext();
85 }
86 @Override
87 public Tuple next() {
88 Object key = wrapped.next();
89 return Tuples.staticArityFlatTupleOf(key);
90 }
91 };
92 };
93 }
94
95 @Override
96 public Collection<Tuple> get(ITuple signature) {
97 Object key = signature.get(0);
98 IMemoryView<Tuple> bucket = columnToTuples.lookup(key);
99 return bucket == null ? null : bucket.distinctValues();
100 }
101
102 @Override
103 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(ITuple signature) {
104 throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!");
105 }
106
107 @Override
108 public boolean remove(Tuple tuple, Tuple signature) {
109 return removeInternal(tuple, tuple.get(keyPosition));
110 }
111
112 @Override
113 public boolean remove(Tuple tuple) {
114 return removeInternal(tuple, tuple.get(keyPosition));
115 }
116
117 @Override
118 public boolean add(Tuple tuple, Tuple signature) {
119 return addInternal(tuple, tuple.get(keyPosition));
120 }
121
122 @Override
123 public boolean add(Tuple tuple) {
124 return addInternal(tuple, tuple.get(keyPosition));
125 }
126
127 protected boolean addInternal(Tuple tuple, Object key) {
128 try {
129 return columnToTuples.addPair(key, tuple) == ChangeGranularity.KEY;
130 } catch (IllegalStateException ex) { // ignore worthless internal exception details
131 throw raiseDuplicateInsertion(tuple);
132 }
133 }
134
135 protected boolean removeInternal(Tuple tuple, Object key) {
136 try {
137 return columnToTuples.removePair(key, tuple) == ChangeGranularity.KEY;
138 } catch (IllegalStateException ex) { // ignore worthless internal exception details
139 throw raiseDuplicateDeletion(tuple);
140 }
141 }
142
143}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java
new file mode 100644
index 00000000..45ce3a4e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java
@@ -0,0 +1,228 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories.timely;
10
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Objects;
16import java.util.Set;
17import java.util.TreeMap;
18
19import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
21import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.matchers.util.Direction;
24import tools.refinery.viatra.runtime.matchers.util.Signed;
25import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
26import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
27import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
28
29/**
30 * Common parts of timely default and timely unary implementations.
31 *
32 * @noextend This class is not intended to be subclassed by clients.
33 * @author Tamas Szabo
34 * @since 2.3
35 */
36abstract class AbstractTimelyMaskedMemory<Timestamp extends Comparable<Timestamp>, KeyType>
37 extends MaskedTupleMemory<Timestamp> {
38
39 protected final TreeMap<Timestamp, Set<KeyType>> foldingStates;
40 protected final Map<KeyType, TimelyMemory<Timestamp>> memoryMap;
41 protected final boolean isLazy;
42
43 public AbstractTimelyMaskedMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
44 super(mask, owner);
45 this.isLazy = isLazy;
46 this.memoryMap = CollectionsFactory.createMap();
47 this.foldingStates = this.isLazy ? CollectionsFactory.createTreeMap() : null;
48 }
49
50 @Override
51 public void initializeWith(final MaskedTupleMemory<Timestamp> other, final Timestamp defaultValue) {
52 final Iterable<Tuple> signatures = other.getSignatures();
53 for (final Tuple signature : signatures) {
54 if (other.isTimely()) {
55 final Map<Tuple, Timeline<Timestamp>> tupleMap = other.getWithTimeline(signature);
56 for (final Entry<Tuple, Timeline<Timestamp>> entry : tupleMap.entrySet()) {
57 for (final Signed<Timestamp> signed : entry.getValue().asChangeSequence()) {
58 if (signed.getDirection() == Direction.DELETE) {
59 this.removeWithTimestamp(entry.getKey(), signed.getPayload());
60 } else {
61 this.addWithTimestamp(entry.getKey(), signed.getPayload());
62 }
63 }
64 }
65 } else {
66 final Collection<Tuple> tuples = other.get(signature);
67 for (final Tuple tuple : tuples) {
68 this.addWithTimestamp(tuple, defaultValue);
69 }
70 }
71 }
72 }
73
74 public boolean isPresentAtInfinityInteral(KeyType key) {
75 final TimelyMemory<Timestamp> values = this.memoryMap.get(key);
76 if (values == null) {
77 return false;
78 } else {
79 return values.getCountAtInfinity() != 0;
80 }
81 }
82
83 @Override
84 public void clear() {
85 this.memoryMap.clear();
86 }
87
88 @Override
89 public int getKeysetSize() {
90 return this.memoryMap.keySet().size();
91 }
92
93 @Override
94 public int getTotalSize() {
95 int i = 0;
96 for (final Entry<KeyType, TimelyMemory<Timestamp>> entry : this.memoryMap.entrySet()) {
97 i += entry.getValue().size();
98 }
99 return i;
100 }
101
102 @Override
103 public Iterator<Tuple> iterator() {
104 return this.memoryMap.values().stream().flatMap(e -> e.keySet().stream()).iterator();
105 }
106
107 protected Collection<Tuple> getInternal(final KeyType key) {
108 final TimelyMemory<Timestamp> memory = this.memoryMap.get(key);
109 if (memory == null) {
110 return null;
111 } else {
112 return memory.getTuplesAtInfinity();
113 }
114 }
115
116 public Map<Tuple, Timeline<Timestamp>> getWithTimestampInternal(final KeyType key) {
117 final TimelyMemory<Timestamp> memory = this.memoryMap.get(key);
118 if (memory == null) {
119 return null;
120 } else {
121 return memory.asMap();
122 }
123 }
124
125 protected Diff<Timestamp> removeInternal(final KeyType key, final Tuple tuple, final Timestamp timestamp) {
126 Timestamp oldResumableTimestamp = null;
127 Timestamp newResumableTimestamp = null;
128
129 final TimelyMemory<Timestamp> keyMemory = this.memoryMap.get(key);
130 if (keyMemory == null) {
131 throw raiseDuplicateDeletion(tuple);
132 }
133
134 if (this.isLazy) {
135 oldResumableTimestamp = keyMemory.getResumableTimestamp();
136 }
137
138 Diff<Timestamp> diff = null;
139 try {
140 diff = keyMemory.remove(tuple, timestamp);
141 } catch (final IllegalStateException e) {
142 throw raiseDuplicateDeletion(tuple);
143 }
144 if (keyMemory.isEmpty()) {
145 this.memoryMap.remove(key);
146 }
147
148 if (this.isLazy) {
149 newResumableTimestamp = keyMemory.getResumableTimestamp();
150 if (!Objects.equals(oldResumableTimestamp, newResumableTimestamp)) {
151 unregisterFoldingState(oldResumableTimestamp, key);
152 registerFoldingState(newResumableTimestamp, key);
153 }
154 }
155
156 return diff;
157 }
158
159 protected Diff<Timestamp> addInternal(final KeyType key, final Tuple tuple, final Timestamp timestamp) {
160 Timestamp oldResumableTimestamp = null;
161 Timestamp newResumableTimestamp = null;
162
163 final TimelyMemory<Timestamp> keyMemory = this.memoryMap.computeIfAbsent(key,
164 k -> new TimelyMemory<Timestamp>(this.isLazy));
165
166 if (this.isLazy) {
167 oldResumableTimestamp = keyMemory.getResumableTimestamp();
168 }
169
170 final Diff<Timestamp> diff = keyMemory.put(tuple, timestamp);
171
172 if (this.isLazy) {
173 newResumableTimestamp = keyMemory.getResumableTimestamp();
174 if (!Objects.equals(oldResumableTimestamp, newResumableTimestamp)) {
175 unregisterFoldingState(oldResumableTimestamp, key);
176 registerFoldingState(newResumableTimestamp, key);
177 }
178 }
179
180 return diff;
181 }
182
183 @Override
184 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
185 return removeWithTimestamp(tuple, null, timestamp);
186 }
187
188 @Override
189 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
190 return addWithTimestamp(tuple, null, timestamp);
191 }
192
193 @Override
194 public boolean isTimely() {
195 return true;
196 }
197
198 protected void registerFoldingState(final Timestamp timestamp, final KeyType key) {
199 if (timestamp != null) {
200 this.foldingStates.compute(timestamp, (k, v) -> {
201 if (v == null) {
202 v = CollectionsFactory.createSet();
203 }
204 v.add(key);
205 return v;
206 });
207 }
208 }
209
210 protected void unregisterFoldingState(final Timestamp timestamp, final KeyType key) {
211 if (timestamp != null) {
212 this.foldingStates.compute(timestamp, (k, v) -> {
213 v.remove(key);
214 return v.isEmpty() ? null : v;
215 });
216 }
217 }
218
219 @Override
220 public Timestamp getResumableTimestamp() {
221 if (this.foldingStates == null || this.foldingStates.isEmpty()) {
222 return null;
223 } else {
224 return this.foldingStates.firstKey();
225 }
226 }
227
228}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java
new file mode 100644
index 00000000..ca06685a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories.timely;
10
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.util.Direction;
20import tools.refinery.viatra.runtime.matchers.util.Signed;
21import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24
25/**
26 * Common parts of timely nullary and timely identity implementations.
27 *
28 * @noextend This class is not intended to be subclassed by clients.
29 * @author Tamas Szabo
30 * @since 2.3
31 */
32abstract class AbstractTimelyTrivialMaskedMemory<Timestamp extends Comparable<Timestamp>> extends MaskedTupleMemory<Timestamp> {
33
34 protected final TimelyMemory<Timestamp> memory;
35
36 protected AbstractTimelyTrivialMaskedMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
37 super(mask, owner);
38 this.memory = new TimelyMemory<Timestamp>(isLazy);
39 }
40
41 @Override
42 public void initializeWith(final MaskedTupleMemory<Timestamp> other, final Timestamp defaultValue) {
43 final Iterable<Tuple> signatures = other.getSignatures();
44 for (final Tuple signature : signatures) {
45 if (other.isTimely()) {
46 final Map<Tuple, Timeline<Timestamp>> tupleMap = other.getWithTimeline(signature);
47 for (final Entry<Tuple, Timeline<Timestamp>> entry : tupleMap.entrySet()) {
48 for (final Signed<Timestamp> signed : entry.getValue().asChangeSequence()) {
49 if (signed.getDirection() == Direction.DELETE) {
50 this.removeWithTimestamp(entry.getKey(), signed.getPayload());
51 } else {
52 this.addWithTimestamp(entry.getKey(), signed.getPayload());
53 }
54 }
55 }
56 } else {
57 final Collection<Tuple> tuples = other.get(signature);
58 for (final Tuple tuple : tuples) {
59 this.removeWithTimestamp(tuple, defaultValue);
60 }
61 }
62 }
63 }
64
65 @Override
66 public void clear() {
67 this.memory.clear();
68 }
69
70 @Override
71 public int getTotalSize() {
72 return this.memory.size();
73 }
74
75 @Override
76 public Iterator<Tuple> iterator() {
77 return this.memory.keySet().iterator();
78 }
79
80 @Override
81 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
82 return removeWithTimestamp(tuple, null, timestamp);
83 }
84
85 @Override
86 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Timestamp timestamp) {
87 return addWithTimestamp(tuple, null, timestamp);
88 }
89
90 @Override
91 public boolean isTimely() {
92 return true;
93 }
94
95 @Override
96 public Timestamp getResumableTimestamp() {
97 return this.memory.getResumableTimestamp();
98 }
99
100}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java
new file mode 100644
index 00000000..623d7399
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java
@@ -0,0 +1,98 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.memories.timely;
11
12import java.util.Collection;
13import java.util.Collections;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24
25/**
26 * Default timely implementation that covers all cases.
27 *
28 * @author Tamas Szabo
29 * @since 2.3
30 */
31public final class TimelyDefaultMaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
32 extends AbstractTimelyMaskedMemory<Timestamp, Tuple> {
33
34 public TimelyDefaultMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
35 super(mask, owner, isLazy);
36 }
37
38 @Override
39 public Iterable<Tuple> getSignatures() {
40 return this.memoryMap.keySet();
41 }
42
43 @Override
44 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Tuple signature,
45 final Timestamp timestamp) {
46 final Tuple key = mask.transform(tuple);
47 return removeInternal(key, tuple, timestamp);
48 }
49
50 @Override
51 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Tuple signature,
52 final Timestamp timestamp) {
53 final Tuple key = this.mask.transform(tuple);
54 return addInternal(key, tuple, timestamp);
55 }
56
57 @Override
58 public Collection<Tuple> get(final ITuple signature) {
59 return getInternal(signature.toImmutable());
60 }
61
62 @Override
63 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(final ITuple signature) {
64 return getWithTimestampInternal(signature.toImmutable());
65 }
66
67 @Override
68 public boolean isPresentAtInfinity(final ITuple signature) {
69 return isPresentAtInfinityInteral(signature.toImmutable());
70 }
71
72 @Override
73 public Set<Tuple> getResumableSignatures() {
74 if (this.foldingStates == null || this.foldingStates.isEmpty()) {
75 return Collections.emptySet();
76 } else {
77 return this.foldingStates.firstEntry().getValue();
78 }
79 }
80
81 @Override
82 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp) {
83 final Map<Tuple, Map<Tuple, Diff<Timestamp>>> result = CollectionsFactory.createMap();
84 final Timestamp resumableTimestamp = this.getResumableTimestamp();
85 if (resumableTimestamp == null || resumableTimestamp.compareTo(timestamp) != 0) {
86 throw new IllegalStateException("Expected to continue folding at " + resumableTimestamp + "!");
87 }
88 final Set<Tuple> signatures = this.foldingStates.remove(timestamp);
89 for (final Tuple signature : signatures) {
90 final TimelyMemory<Timestamp> memory = this.memoryMap.get(signature);
91 final Map<Tuple, Diff<Timestamp>> diffMap = memory.resumeAt(resumableTimestamp);
92 result.put(signature, diffMap);
93 registerFoldingState(memory.getResumableTimestamp(), signature);
94 }
95 return result;
96 }
97
98}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java
new file mode 100644
index 00000000..568f274d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
21import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
23
24/**
25 * Timely specialization for identity mask.
26 *
27 * @author Tamas Szabo
28 * @since 2.3
29 */
30public final class TimelyIdentityMaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
31 extends AbstractTimelyTrivialMaskedMemory<Timestamp> {
32
33 public TimelyIdentityMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
34 super(mask, owner, isLazy);
35 if (!mask.isIdentity())
36 throw new IllegalArgumentException(mask.toString());
37 }
38
39 @Override
40 public int getKeysetSize() {
41 return this.memory.size();
42 }
43
44 @Override
45 public Iterable<Tuple> getSignatures() {
46 return this.memory.keySet();
47 }
48
49 @Override
50 public Collection<Tuple> get(final ITuple signature) {
51 if (this.memory.getTuplesAtInfinity().contains(signature)) {
52 return Collections.singleton(signature.toImmutable());
53 } else {
54 return null;
55 }
56 }
57
58 @Override
59 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(final ITuple signature) {
60 final Timeline<Timestamp> value = this.memory.get(signature);
61 if (value != null) {
62 return Collections.singletonMap(signature.toImmutable(), value);
63 } else {
64 return null;
65 }
66 }
67
68 @Override
69 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
70 try {
71 return this.memory.remove(tuple, timestamp);
72 } catch (final IllegalStateException e) {
73 throw raiseDuplicateDeletion(tuple);
74 }
75 }
76
77 @Override
78 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
79 return this.memory.put(tuple, timestamp);
80 }
81
82 @Override
83 public boolean isPresentAtInfinity(final ITuple signature) {
84 return this.memory.isPresentAtInfinity(signature.toImmutable());
85 }
86
87 @Override
88 public Set<Tuple> getResumableSignatures() {
89 if (this.memory.getResumableTimestamp() != null) {
90 return this.memory.getResumableTuples();
91 } else {
92 return Collections.emptySet();
93 }
94 }
95
96 @Override
97 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp) {
98 final Map<Tuple, Diff<Timestamp>> diffMap = this.memory.resumeAt(timestamp);
99 final Map<Tuple, Map<Tuple, Diff<Timestamp>>> result = CollectionsFactory.createMap();
100 for (final Entry<Tuple, Diff<Timestamp>> entry : diffMap.entrySet()) {
101 result.put(entry.getKey(), Collections.singletonMap(entry.getKey(), entry.getValue()));
102 }
103 return result;
104 }
105
106}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java
new file mode 100644
index 00000000..75987a89
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java
@@ -0,0 +1,108 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
20import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
21import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
22
23/**
24 * Timely specialization for nullary mask.
25 *
26 * @author Tamas Szabo
27 * @since 2.3
28 */
29public final class TimelyNullaryMaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
30 extends AbstractTimelyTrivialMaskedMemory<Timestamp> {
31
32 protected static final Tuple EMPTY_TUPLE = Tuples.staticArityFlatTupleOf();
33 protected static final Set<Tuple> UNIT_RELATION = Collections.singleton(EMPTY_TUPLE);
34 protected static final Set<Tuple> EMPTY_RELATION = Collections.emptySet();
35
36 public TimelyNullaryMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
37 super(mask, owner, isLazy);
38 if (0 != mask.getSize()) {
39 throw new IllegalArgumentException(mask.toString());
40 }
41 }
42
43 @Override
44 public int getKeysetSize() {
45 return this.memory.isEmpty() ? 0 : 1;
46 }
47
48 @Override
49 public Iterable<Tuple> getSignatures() {
50 return this.memory.isEmpty() ? EMPTY_RELATION : UNIT_RELATION;
51 }
52
53 @Override
54 public Collection<Tuple> get(final ITuple signature) {
55 if (0 == signature.getSize()) {
56 return this.memory.getTuplesAtInfinity();
57 } else {
58 return null;
59 }
60 }
61
62 @Override
63 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(final ITuple signature) {
64 if (0 == signature.getSize()) {
65 return this.memory.asMap();
66 } else {
67 return null;
68 }
69 }
70
71 @Override
72 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
73 try {
74 return this.memory.remove(tuple, timestamp);
75 } catch (final IllegalStateException e) {
76 throw raiseDuplicateDeletion(tuple);
77 }
78 }
79
80 @Override
81 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
82 return this.memory.put(tuple, timestamp);
83 }
84
85 @Override
86 public boolean isPresentAtInfinity(final ITuple signature) {
87 if (0 == signature.getSize()) {
88 return this.memory.getCountAtInfinity() > 0;
89 } else {
90 return false;
91 }
92 }
93
94 @Override
95 public Set<Tuple> getResumableSignatures() {
96 if (this.memory.getResumableTimestamp() != null) {
97 return UNIT_RELATION;
98 } else {
99 return EMPTY_RELATION;
100 }
101 }
102
103 @Override
104 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp) {
105 return Collections.singletonMap(EMPTY_TUPLE, this.memory.resumeAt(timestamp));
106 }
107
108}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java
new file mode 100644
index 00000000..178193af
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java
@@ -0,0 +1,133 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.memories.timely;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Iterator;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
21import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
22import tools.refinery.viatra.runtime.matchers.util.TimelyMemory;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
25
26/**
27 * Timely specialization for unary mask.
28 *
29 * @author Tamas Szabo
30 * @since 2.3
31 */
32public final class TimelyUnaryMaskedTupleMemory<Timestamp extends Comparable<Timestamp>>
33 extends AbstractTimelyMaskedMemory<Timestamp, Object> {
34
35 protected final int keyPosition;
36
37 public TimelyUnaryMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) {
38 super(mask, owner, isLazy);
39 if (1 != mask.getSize())
40 throw new IllegalArgumentException(mask.toString());
41 this.keyPosition = mask.indices[0];
42 }
43
44 @Override
45 public Iterable<Tuple> getSignatures() {
46 return () -> {
47 final Iterator<Object> wrapped = this.memoryMap.keySet().iterator();
48 return new Iterator<Tuple>() {
49 @Override
50 public boolean hasNext() {
51 return wrapped.hasNext();
52 }
53
54 @Override
55 public Tuple next() {
56 final Object key = wrapped.next();
57 return Tuples.staticArityFlatTupleOf(key);
58 }
59 };
60 };
61 }
62
63 @Override
64 public Diff<Timestamp> removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
65 final Object key = tuple.get(keyPosition);
66 return removeInternal(key, tuple, timestamp);
67 }
68
69 @Override
70 public Diff<Timestamp> addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) {
71 final Object key = tuple.get(keyPosition);
72 return addInternal(key, tuple, timestamp);
73 }
74
75 @Override
76 public Collection<Tuple> get(final ITuple signature) {
77 return getInternal(signature.get(0));
78 }
79
80 @Override
81 public Map<Tuple, Timeline<Timestamp>> getWithTimeline(final ITuple signature) {
82 return getWithTimestampInternal(signature.get(0));
83 }
84
85 @Override
86 public boolean isPresentAtInfinity(ITuple signature) {
87 return isPresentAtInfinityInteral(signature.get(0));
88 }
89
90 @Override
91 public Iterable<Tuple> getResumableSignatures() {
92 if (this.foldingStates == null || this.foldingStates.isEmpty()) {
93 return Collections.emptySet();
94 } else {
95 return () -> {
96 final Iterator<Object> wrapped = this.foldingStates.firstEntry().getValue().iterator();
97 return new Iterator<Tuple>() {
98 @Override
99 public boolean hasNext() {
100 return wrapped.hasNext();
101 }
102
103 @Override
104 public Tuple next() {
105 final Object key = wrapped.next();
106 return Tuples.staticArityFlatTupleOf(key);
107 }
108 };
109 };
110 }
111 }
112
113 @Override
114 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp) {
115 final Map<Tuple, Map<Tuple, Diff<Timestamp>>> result = CollectionsFactory.createMap();
116 final Timestamp resumableTimestamp = this.getResumableTimestamp();
117 if (resumableTimestamp == null) {
118 throw new IllegalStateException("There is nothing to fold!");
119 } else if (resumableTimestamp.compareTo(timestamp) != 0) {
120 throw new IllegalStateException("Expected to continue folding at " + resumableTimestamp + "!");
121 }
122
123 final Set<Object> signatures = this.foldingStates.remove(timestamp);
124 for (final Object signature : signatures) {
125 final TimelyMemory<Timestamp> memory = this.memoryMap.get(signature);
126 final Map<Tuple, Diff<Timestamp>> diffMap = memory.resumeAt(resumableTimestamp);
127 result.put(Tuples.staticArityFlatTupleOf(signature), diffMap);
128 registerFoldingState(memory.getResumableTimestamp(), signature);
129 }
130 return result;
131 }
132
133}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java
new file mode 100644
index 00000000..c9f4b305
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java
@@ -0,0 +1,108 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.planning;
11
12import java.util.Map;
13
14import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
15import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
18import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
19
20/**
21 *
22 * An implicit common parameter is the "effort" PatternDescription. This
23 * indicates that the build request is part of an effort to build the matcher of
24 * the given pattern; it it important to record this during code generation so
25 * that the generated code can be separated according to patterns.
26 *
27 * @param <Collector>
28 * the handle of a receiver-like RETE ending to which plans can be
29 * connected
30 * @author Gabor Bergmann
31 * @noimplement This interface is not intended to be implemented by clients.
32 */
33public interface IOperationCompiler<Collector> {
34
35 /**
36 * @throws ViatraQueryRuntimeException
37 */
38 public Collector patternCollector(PQuery pattern);
39
40 public void buildConnection(SubPlan parentPlan, Collector collector);
41
42 /**
43 * @since 0.9
44 */
45 public void patternFinished(PQuery pattern, Collector collector);
46
47 /**
48 * @throws ViatraQueryRuntimeException
49 */
50 public SubPlan patternCallPlan(Tuple nodes, PQuery supplierKey);
51
52 public SubPlan transitiveInstantiationPlan(Tuple nodes);
53
54 public SubPlan directInstantiationPlan(Tuple nodes);
55
56 public SubPlan transitiveGeneralizationPlan(Tuple nodes);
57
58 public SubPlan directGeneralizationPlan(Tuple nodes);
59
60 public SubPlan transitiveContainmentPlan(Tuple nodes);
61
62 public SubPlan directContainmentPlan(Tuple nodes);
63
64 public SubPlan binaryEdgeTypePlan(Tuple nodes, Object supplierKey);
65
66 public SubPlan ternaryEdgeTypePlan(Tuple nodes, Object supplierKey);
67
68 public SubPlan unaryTypePlan(Tuple nodes, Object supplierKey);
69
70 public SubPlan buildStartingPlan(Object[] constantValues, Object[] constantNames);
71
72 public SubPlan buildEqualityChecker(SubPlan parentPlan, int[] indices);
73
74 public SubPlan buildInjectivityChecker(SubPlan parentPlan, int subject, int[] inequalIndices);
75
76 public SubPlan buildTransitiveClosure(SubPlan parentPlan);
77
78 public SubPlan buildTrimmer(SubPlan parentPlan, TupleMask trimMask, boolean enforceUniqueness);
79
80 public SubPlan buildBetaNode(SubPlan primaryPlan, SubPlan sidePlan,
81 TupleMask primaryMask, TupleMask sideMask, TupleMask complementer, boolean negative);
82
83 public SubPlan buildCounterBetaNode(SubPlan primaryPlan, SubPlan sidePlan,
84 TupleMask primaryMask, TupleMask originalSideMask, TupleMask complementer,
85 Object aggregateResultCalibrationElement);
86
87 public SubPlan buildCountCheckBetaNode(SubPlan primaryPlan, SubPlan sidePlan,
88 TupleMask primaryMask, TupleMask originalSideMask, int resultPositionInSignature);
89
90 public SubPlan buildPredicateChecker(IExpressionEvaluator evaluator, Map<String, Integer> tupleNameMap,
91 SubPlan parentPlan);
92 public SubPlan buildFunctionEvaluator(IExpressionEvaluator evaluator, Map<String, Integer> tupleNameMap,
93 SubPlan parentPlan, Object computedResultCalibrationElement);
94
95 /**
96 * @return an operation compiler that potentially acts on a separate container
97 */
98 public IOperationCompiler<Collector> getNextContainer();
99
100 /**
101 * @return an operation compiler that puts build actions on the tab of the given pattern
102 * @since 0.9
103 */
104 public IOperationCompiler<Collector> putOnTab(PQuery effort /*, IPatternMatcherContext context*/);
105
106 public void reinitialize();
107
108} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java
new file mode 100644
index 00000000..6ce9d91b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.planning;
11
12import org.apache.log4j.Logger;
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
15import tools.refinery.viatra.runtime.matchers.psystem.PBody;
16
17/**
18 * An algorithm that builds a query plan based on a PSystem representation of a body of constraints. This interface is
19 * for internal use of the various query backends.
20 *
21 * @author Gabor Bergmann
22 */
23public interface IQueryPlannerStrategy {
24
25 /**
26 * @throws ViatraQueryRuntimeException
27 */
28 public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context);
29}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java
new file mode 100644
index 00000000..501ddf73
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning;
10
11import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
12
13/**
14 * @author Zoltan Ujhelyi
15 * @since 0.9
16 */
17public class QueryProcessingException extends ViatraQueryRuntimeException {
18
19 private static final long serialVersionUID = -8272290113656867086L;
20 /**
21 * Binding the '{n}' (n = 1..N) strings to contextual conditions in 'context'
22 *
23 * @param context
24 * : array of context-sensitive Strings
25 */
26 protected static String bind(String message, String[] context) {
27 if (context == null)
28 return message;
29
30 String internal = message;
31 for (int i = 0; i < context.length; i++) {
32 internal = internal.replace("{" + (i + 1) + "}", context[i] != null ? context[i] : "<<null>>");
33 }
34 return internal;
35 }
36
37 private Object patternDescription;
38 private String shortMessage;
39
40 /**
41 * @param message
42 * The template of the exception message
43 * @param context
44 * The data elements to be used to instantiate the template. Can be null if no context parameter is
45 * defined
46 * @param patternDescription
47 * the PatternDescription where the exception occurred
48 * @since 2.0
49 */
50 public QueryProcessingException(String message, Object patternDescription) {
51 super(message);
52 initializeFields(message, patternDescription);
53 }
54
55 /**
56 * @param message
57 * The template of the exception message
58 * @param context
59 * The data elements to be used to instantiate the template. Can be null if no context parameter is
60 * defined
61 * @param patternDescription
62 * the PatternDescription where the exception occurred
63 */
64 public QueryProcessingException(String message, String[] context, String shortMessage, Object patternDescription) {
65 super(bind(message, context));
66 initializeFields(shortMessage, patternDescription);
67 }
68
69 /**
70 * @param message
71 * The template of the exception message
72 * @param context
73 * The data elements to be used to instantiate the template. Can be null if no context parameter is
74 * defined
75 * @param patternDescription
76 * the PatternDescription where the exception occurred
77 */
78 public QueryProcessingException(String message, String[] context, String shortMessage, Object patternDescription,
79 Throwable cause) {
80 super(bind(message, context), cause);
81 initializeFields(shortMessage, patternDescription);
82 }
83
84 public Object getPatternDescription() {
85 return patternDescription;
86 }
87
88 public String getShortMessage() {
89 return shortMessage;
90 }
91
92 private void initializeFields(String shortMessage, Object patternDescription) {
93 this.patternDescription = patternDescription;
94 this.shortMessage = shortMessage;
95 }
96
97
98 public void setPatternDescription(Object patternDescription) {
99 this.patternDescription = patternDescription;
100 }
101
102} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java
new file mode 100644
index 00000000..1998df9d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java
@@ -0,0 +1,240 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.planning;
11
12import java.util.Arrays;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Set;
16import java.util.WeakHashMap;
17import java.util.stream.Collectors;
18
19import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
20import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper;
21import tools.refinery.viatra.runtime.matchers.planning.operations.POperation;
22import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
23import tools.refinery.viatra.runtime.matchers.planning.operations.PStart;
24import tools.refinery.viatra.runtime.matchers.psystem.PBody;
25import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
26import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
27import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
28
29/**
30 * A plan representing a subset of (or possibly all the) constraints evaluated. A SubPlan instance is responsible for
31 * representing a state of the plan; but after it is initialized it is expected be immutable
32 * (exception: inferred constraints, see {@link #inferConstraint(PConstraint)}).
33 *
34 * <p> A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans.
35 * Important maintained information: <ul>
36 * <li>set of <b>variables</b> whose values are known when the runtime evaluation is at this stage,
37 * <li>set of <b>constraints</b> that are known to hold true at this point.
38 * </ul>
39 *
40 * <p> Recommended to instantiate via a {@link SubPlanFactory} or subclasses,
41 * so that query planners can subclass SubPlan if needed.
42 *
43 * @author Gabor Bergmann
44 *
45 */
46public class SubPlan {
47 private PBody body;
48 private List<? extends SubPlan> parentPlans;
49 private POperation operation;
50
51 private final Set<PVariable> visibleVariables;
52 private final Set<PVariable> allVariables;
53 private final Set<PVariable> introducedVariables; // delta compared to first parent
54 private Set<PConstraint> allConstraints;
55 private Set<PConstraint> deltaConstraints; // delta compared to all parents
56 private Set<PConstraint> externallyInferredConstraints; // inferred in addition to direct consequences of the operation and parents
57
58
59
60
61
62 /**
63 * A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans.
64 */
65 public SubPlan(PBody body, POperation operation, SubPlan... parentPlans) {
66 this(body, operation, Arrays.asList(parentPlans));
67 }
68 /**
69 * A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans.
70 */
71 public SubPlan(PBody body, POperation operation, List<? extends SubPlan> parentPlans) {
72 super();
73 this.body = body;
74 this.parentPlans = parentPlans;
75 this.operation = operation;
76
77 this.externallyInferredConstraints = new HashSet<PConstraint>();
78 this.deltaConstraints = new HashSet<PConstraint>(operation.getDeltaConstraints());
79
80 this.allVariables = new HashSet<PVariable>();
81 for (PConstraint constraint: deltaConstraints) {
82 this.allVariables.addAll(constraint.getDeducedVariables());
83 }
84 this.allConstraints = new HashSet<PConstraint>(deltaConstraints);
85 for (SubPlan parentPlan: parentPlans) {
86 this.allConstraints.addAll(parentPlan.getAllEnforcedConstraints());
87 this.allVariables.addAll(parentPlan.getAllDeducedVariables());
88 }
89
90 // TODO this is ugly a bit
91 if (operation instanceof PStart) {
92 this.visibleVariables = new HashSet<PVariable>(((PStart) operation).getAPrioriVariables());
93 this.allVariables.addAll(visibleVariables);
94 } else if (operation instanceof PProject) {
95 this.visibleVariables = new HashSet<PVariable>(((PProject) operation).getToVariables());
96 } else {
97 this.visibleVariables = new HashSet<PVariable>();
98 for (SubPlan parentPlan: parentPlans)
99 this.visibleVariables.addAll(parentPlan.getVisibleVariables());
100 for (PConstraint constraint: deltaConstraints)
101 this.visibleVariables.addAll(constraint.getDeducedVariables());
102 }
103
104 this.introducedVariables = new HashSet<PVariable>(this.visibleVariables);
105 if (!parentPlans.isEmpty())
106 introducedVariables.removeAll(parentPlans.get(0).getVisibleVariables());
107
108 operation.checkConsistency(this);
109 }
110
111
112 @Override
113 public String toString() {
114 return toLongString();
115 }
116 public String toShortString() {
117 return String.format("Plan{%s}:%s",
118 visibleVariables.stream().map(PVariable::getName).collect(Collectors.joining(",")),
119 operation.getShortName());
120 }
121 public String toLongString() {
122 return String.format("%s<%s>",
123 toShortString(),
124 parentPlans.stream().map(Object::toString).collect(Collectors.joining("; ")));
125 }
126
127
128 /**
129 * All constraints that are known to hold at this point
130 */
131 public Set<PConstraint> getAllEnforcedConstraints() {
132 return allConstraints;
133 }
134
135 /**
136 * The new constraints enforced at this stage of plan, that aren't yet enforced at parents
137 * (results are also included in {@link SubPlan#getAllEnforcedConstraints()})
138 */
139 public Set<PConstraint> getDeltaEnforcedConstraints() {
140 return deltaConstraints;
141 }
142
143 /**
144 * Indicate that a given constraint was found to be automatically satisfied at this point
145 * without additional operations.
146 * (results are also included in {@link SubPlan#getDeltaEnforcedConstraints()})
147 *
148 * <p>Warning: not propagated automatically to child plans,
149 * so best to invoke before constructing further SubPlans. </p>
150 */
151 public void inferConstraint(PConstraint constraint) {
152 externallyInferredConstraints.add(constraint);
153 deltaConstraints.add(constraint);
154 allConstraints.add(constraint);
155 }
156
157 public PBody getBody() {
158 return body;
159 }
160
161 /**
162 * Variables which are assigned a value at this point
163 * (results are also included in {@link SubPlan#getAllDeducedVariables()})
164 */
165 public Set<PVariable> getVisibleVariables() {
166 return visibleVariables;
167 }
168 /**
169 * Variables which have been assigned a value;
170 * includes visible variables (see {@link #getVisibleVariables()})
171 * and additionally any variables hidden by a projection (see {@link PProject}).
172 */
173 public Set<PVariable> getAllDeducedVariables() {
174 return allVariables;
175 }
176 /**
177 * Delta compared to first parent: variables that are visible here but were not visible at the first parent.
178 */
179 public Set<PVariable> getIntroducedVariables() {
180 return introducedVariables;
181 }
182 public List<? extends SubPlan> getParentPlans() {
183 return parentPlans;
184 }
185 public POperation getOperation() {
186 return operation;
187 }
188
189
190 /**
191 * The closure of all type judgments of enforced constraints at this point.
192 * <p> No subsumption applied.
193 */
194 public Set<TypeJudgement> getAllImpliedTypeJudgements(IQueryMetaContext context) {
195 Set<TypeJudgement> impliedJudgements = allImpliedTypeJudgements.get(context);
196 if (impliedJudgements == null) {
197 Set<TypeJudgement> equivalentJudgements = TypeHelper.getDirectJudgements(getAllEnforcedConstraints(), context);
198 impliedJudgements = TypeHelper.typeClosure(equivalentJudgements, context);
199
200 allImpliedTypeJudgements.put(context, impliedJudgements);
201 }
202 return impliedJudgements;
203 }
204 private WeakHashMap<IQueryMetaContext, Set<TypeJudgement>> allImpliedTypeJudgements = new WeakHashMap<IQueryMetaContext, Set<TypeJudgement>>();
205
206
207 @Override
208 public int hashCode() {
209 final int prime = 31;
210 int result = 1;
211 result = prime * result
212 + ((operation == null) ? 0 : operation.hashCode());
213 result = prime * result
214 + ((parentPlans == null) ? 0 : parentPlans.hashCode());
215 return result;
216 }
217 @Override
218 public boolean equals(Object obj) {
219 if (this == obj)
220 return true;
221 if (obj == null)
222 return false;
223 if (!(obj instanceof SubPlan))
224 return false;
225 SubPlan other = (SubPlan) obj;
226 if (operation == null) {
227 if (other.operation != null)
228 return false;
229 } else if (!operation.equals(other.operation))
230 return false;
231 if (parentPlans == null) {
232 if (other.parentPlans != null)
233 return false;
234 } else if (!parentPlans.equals(other.parentPlans))
235 return false;
236 return true;
237 }
238
239
240}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java
new file mode 100644
index 00000000..d0df5fac
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning;
10
11import tools.refinery.viatra.runtime.matchers.planning.operations.POperation;
12import tools.refinery.viatra.runtime.matchers.psystem.PBody;
13
14/**
15 * Single entry point for creating subplans.
16 * Can be subclassed by query planner to provide specialized SubPlans.
17 * @author Bergmann Gabor
18 *
19 */
20public class SubPlanFactory {
21
22 protected PBody body;
23
24 public SubPlanFactory(PBody body) {
25 super();
26 this.body = body;
27 }
28
29 public SubPlan createSubPlan(POperation operation, SubPlan... parentPlans) {
30 return new SubPlan(body, operation, parentPlans);
31 }
32
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java
new file mode 100644
index 00000000..ed5d1cbb
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java
@@ -0,0 +1,165 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.planning.helpers;
11
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
18import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
19import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
20import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
21import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory;
22import tools.refinery.viatra.runtime.matchers.planning.operations.PProject;
23import tools.refinery.viatra.runtime.matchers.psystem.PBody;
24import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
25import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
26import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
27import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
28
29/**
30 * @author Gabor Bergmann
31 *
32 */
33public class BuildHelper {
34
35 private BuildHelper() {
36 // Hiding constructor for utility class
37 }
38
39// public static SubPlan naturalJoin(IOperationCompiler buildable,
40// SubPlan primaryPlan, SubPlan secondaryPlan) {
41// JoinHelper joinHelper = new JoinHelper(primaryPlan, secondaryPlan);
42// return buildable.buildBetaNode(primaryPlan, secondaryPlan, joinHelper.getPrimaryMask(),
43// joinHelper.getSecondaryMask(), joinHelper.getComplementerMask(), false);
44// }
45
46
47 /**
48 * Reduces the number of tuples by trimming (existentially quantifying) the set of variables that <ul>
49 * <li> are visible in the subplan,
50 * <li> are not exported parameters,
51 * <li> have all their constraints already enforced in the subplan,
52 * </ul> and thus will not be needed anymore.
53 *
54 * @param onlyIfNotDetermined if true, no trimming performed unless there is at least one variable that is not functionally determined
55 * @return the plan after the trimming (possibly the original)
56 * @since 1.5
57 */
58 public static SubPlan trimUnneccessaryVariables(SubPlanFactory planFactory, /*IOperationCompiler buildable,*/
59 SubPlan plan, boolean onlyIfNotDetermined, QueryAnalyzer analyzer) {
60 Set<PVariable> canBeTrimmed = new HashSet<PVariable>();
61 Set<PVariable> variablesInPlan = plan.getVisibleVariables();
62 for (PVariable trimCandidate : variablesInPlan) {
63 if (trimCandidate.getReferringConstraintsOfType(ExportedParameter.class).isEmpty()) {
64 if (plan.getAllEnforcedConstraints().containsAll(trimCandidate.getReferringConstraints()))
65 canBeTrimmed.add(trimCandidate);
66 }
67 }
68 final Set<PVariable> retainedVars = setMinus(variablesInPlan, canBeTrimmed);
69 if (!canBeTrimmed.isEmpty() && !(onlyIfNotDetermined && areVariablesDetermined(plan, retainedVars, canBeTrimmed, analyzer, false))) {
70 // TODO add smart ordering?
71 plan = planFactory.createSubPlan(new PProject(retainedVars), plan);
72 }
73 return plan;
74 }
75
76 /**
77 * @return true iff a set of given variables functionally determine all visible variables in the subplan according to the subplan's constraints
78 * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation;
79 * if false, user-provided soft dependencies are included as well, that are anticipated but not guaranteed by the storage mechanism;
80 * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance
81 * @since 1.5
82 */
83 public static boolean areAllVariablesDetermined(SubPlan plan, Collection<PVariable> determining, QueryAnalyzer analyzer, boolean strict) {
84 return areVariablesDetermined(plan, determining, plan.getVisibleVariables(), analyzer, strict);
85 }
86
87 /**
88 * @return true iff one set of given variables functionally determine the other set according to the subplan's constraints
89 * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation;
90 * if false, user-provided soft dependencies are included as well, that are anticipated but not guaranteed by the storage mechanism;
91 * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance
92 * @since 1.5
93 */
94 public static boolean areVariablesDetermined(SubPlan plan, Collection<PVariable> determining, Collection<PVariable> determined,
95 QueryAnalyzer analyzer, boolean strict) {
96 Map<Set<PVariable>, Set<PVariable>> dependencies = analyzer.getFunctionalDependencies(plan.getAllEnforcedConstraints(), strict);
97 final Set<PVariable> closure = FunctionalDependencyHelper.closureOf(determining, dependencies);
98 final boolean isDetermined = closure.containsAll(determined);
99 return isDetermined;
100 }
101
102 private static <T> Set<T> setMinus(Set<T> a, Set<T> b) {
103 Set<T> difference = new HashSet<T>(a);
104 difference.removeAll(b);
105 return difference;
106 }
107
108 /**
109 * Finds an arbitrary constraint that is not enforced at the given plan.
110 *
111 * @param pSystem
112 * @param plan
113 * @return a PConstraint that is not enforced, if any, or null if all are enforced
114 */
115 public static PConstraint getAnyUnenforcedConstraint(PBody pSystem,
116 SubPlan plan) {
117 Set<PConstraint> allEnforcedConstraints = plan.getAllEnforcedConstraints();
118 Set<PConstraint> constraints = pSystem.getConstraints();
119 for (PConstraint pConstraint : constraints) {
120 if (!allEnforcedConstraints.contains(pConstraint))
121 return pConstraint;
122 }
123 return null;
124 }
125
126 /**
127 * Skips the last few steps, if any, that are projections, so that a custom projection can be added instead.
128 * Useful for connecting body final plans into the production node.
129 *
130 * @since 2.1
131 */
132 public static SubPlan eliminateTrailingProjections(SubPlan plan) {
133 while (plan.getOperation() instanceof PProject)
134 plan = plan.getParentPlans().get(0);
135 return plan;
136 }
137
138 /**
139 * Verifies whether all constraints are enforced and exported parameters are present.
140 *
141 * @param pSystem
142 * @param plan
143 * @throws ViatraQueryRuntimeException
144 */
145 public static void finalCheck(final PBody pSystem, SubPlan plan, IQueryMetaContext context) {
146 PConstraint unenforcedConstraint = getAnyUnenforcedConstraint(pSystem, plan);
147 if (unenforcedConstraint != null) {
148 throw new QueryProcessingException(
149 "Pattern matcher construction terminated without successfully enforcing constraint {1}."
150 + " Could be caused if the value of some variables can not be deduced, e.g. by circularity of pattern constraints.",
151 new String[] { unenforcedConstraint.toString() }, "Could not enforce a pattern constraint", null);
152 }
153 for (ExportedParameter export : pSystem
154 .getConstraintsOfType(ExportedParameter.class)) {
155 if (!export.isReadyAt(plan, context)) {
156 throw new QueryProcessingException(
157 "Exported pattern parameter {1} could not be deduced during pattern matcher construction."
158 + " A pattern constraint is required to positively deduce its value.",
159 new String[] { export.getParameterName() }, "Could not calculate pattern parameter",
160 null);
161 }
162 }
163 }
164
165}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java
new file mode 100644
index 00000000..40835f52
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java
@@ -0,0 +1,143 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Adam Dudas, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.helpers;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.Set;
18
19import tools.refinery.viatra.runtime.matchers.util.Sets;
20
21/**
22 * Helper utility class for functional dependency analysis.
23 *
24 * Throughout this class attribute sets are represented as generic sets and functional dependencies as maps from
25 * attribute set (generic sets) to attribute set (generic sets)
26 *
27 * @author Adam Dudas
28 *
29 */
30public class FunctionalDependencyHelper {
31
32 private FunctionalDependencyHelper() {
33 // Hiding constructor for utility class
34 }
35
36 /**
37 * Get the closure of the specified attribute set relative to the specified functional dependencies.
38 *
39 * @param attributes
40 * The attributes to get the closure of.
41 * @param dependencies
42 * The functional dependencies of which the closure operation is relative to.
43 * @return The closure of the specified attribute set relative to the specified functional dependencies.
44 */
45 public static <A> Set<A> closureOf(Collection<A> attributes, Map<Set<A>, Set<A>> dependencies) {
46 Set<A> closureSet = new HashSet<A>();
47
48 for (Set<A> closureSet1 = new HashSet<A>(attributes); closureSet.addAll(closureSet1);) {
49 closureSet1 = new HashSet<A>();
50 for (Entry<Set<A>, Set<A>> dependency : dependencies.entrySet()) {
51 if (closureSet.containsAll(dependency.getKey()))
52 closureSet1.addAll(dependency.getValue());
53 }
54 }
55
56 return closureSet;
57 }
58
59 /**
60 * @return true if the dependency from the left set to the right set is trivial
61 * @since 1.5
62 */
63 public static <A> boolean isTrivial(Set<A> left, Set<A> right) {
64 return left.containsAll(right);
65 }
66
67 /***
68 * Returns the dependency set over attributes in {@link targetAttributes} that are implied by a given source dependency set.
69 * <p> Note: exponential in the size of the target attribute set.
70 * <p> Note: minimality of the returned dependency set is currently not guaranteed.
71 * @param originalDependencies all dependencies that are known to hold on a wider set of attributes
72 * @param targetAttributes the set of attributes we are interested in
73 * @since 1.5
74 */
75 public static <A> Map<Set<A>, Set<A>> projectDependencies(Map<Set<A>, Set<A>> originalDependencies, Set<A> targetAttributes) {
76 // only those attributes are considered as left-hand-side candidates that occur at least once in dependencies
77 Set<A> leftCandidates = new HashSet<A>();
78 for (Entry<Set<A>, Set<A>> dependency : originalDependencies.entrySet()) {
79 if (!isTrivial(dependency.getKey(), dependency.getValue())) // only if non-trivial
80 leftCandidates.addAll(Sets.intersection(dependency.getKey(), targetAttributes));
81 }
82
83 // Compute an initial list of nontrivial projected dependencies - it does not have to be minimal yet
84 Map<Set<A>, Set<A>> initialDependencies = new HashMap<Set<A>, Set<A>>();
85 for (Set<A> leftSet : Sets.powerSet(leftCandidates)) {
86 Set<A> rightSet = Sets.intersection(closureOf(leftSet, originalDependencies), targetAttributes);
87 if (!isTrivial(leftSet, rightSet)) {
88 initialDependencies.put(leftSet, rightSet);
89 }
90 }
91 // Don't forget to include constants!
92 Set<A> constants = Sets.intersection(closureOf(Collections.<A>emptySet(), originalDependencies), targetAttributes);
93 if (! constants.isEmpty()) {
94 initialDependencies.put(Collections.<A>emptySet(), constants);
95 }
96
97 // Omit those dependencies where the LHS has superfluous attributes
98 Map<Set<A>, Set<A>> solidDependencies = new HashMap<Set<A>, Set<A>>();
99 for (Entry<Set<A>, Set<A>> dependency : initialDependencies.entrySet()) {
100 Set<A> leftSet = dependency.getKey();
101 Set<A> rightSet = dependency.getValue();
102 boolean solid = true;
103 for (A skipped : leftSet) { // what if we skip one attribute from the left set?
104 Set<A> singleton = Collections.singleton(skipped);
105 Set<A> candidate = Sets.difference(leftSet, singleton);
106 Set<A> rightCandidate = initialDependencies.get(candidate);
107 if (rightCandidate != null) {
108 if (Sets.union(rightCandidate, singleton).containsAll(rightSet)) {
109 solid = false;
110 break;
111 }
112 }
113 }
114 if (solid) {
115 solidDependencies.put(leftSet, rightSet);
116 }
117 }
118
119 // TODO perform proper minimization,
120 // see e.g. page 45 in http://www.cs.ubc.ca/~hkhosrav/db/slides/03.design%20theory.pdf
121
122 return Collections.unmodifiableMap(solidDependencies);
123 }
124
125 /**
126 * Adds a given dependency to a mutable accumulator.
127 * @since 1.5
128 */
129 public static <A> void includeDependency(Map<Set<A>, Set<A>> accumulator, Set<A> left, Set<A> right) {
130 Set<A> accumulatorRights = accumulator.computeIfAbsent(left, l -> new HashSet<>());
131 accumulatorRights.addAll(right);
132 }
133
134 /**
135 * Adds all given dependencies to a mutable accumulator.
136 * @since 1.5
137 */
138 public static <A> void includeDependencies(Map<Set<A>, Set<A>> accumulator, Map<Set<A>, Set<A>> additionalDependencies) {
139 for (Entry<Set<A>, Set<A>> entry : additionalDependencies.entrySet()) {
140 includeDependency(accumulator, entry.getKey(), entry.getValue());
141 }
142 }
143}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java
new file mode 100644
index 00000000..b4f848a7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java
@@ -0,0 +1,62 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.helpers;
10
11import java.util.Optional;
12import java.util.function.BiFunction;
13
14import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
15import tools.refinery.viatra.runtime.matchers.util.Accuracy;
16
17/**
18 * Helpers dealing with optionally present statistics information
19 *
20 * @author Gabor Bergmann
21 * @since 2.1
22 *
23 */
24public class StatisticsHelper {
25
26 private StatisticsHelper() {
27 // Hidden utility class constructor
28 }
29
30 public static Optional<Double> estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy,
31 BiFunction<TupleMask, Accuracy, Optional<Long>> estimateCardinality)
32 {
33 if (groupMask.isIdentity()) return Optional.of(1.0);
34
35 Accuracy numeratorAccuracy = requiredAccuracy;
36 Accuracy denominatorAccuracy = requiredAccuracy.reciprocal();
37 TupleMask identityMask = TupleMask.identity(groupMask.sourceWidth);
38
39 Optional<Long> totalCountEstimate = estimateCardinality.apply(identityMask, numeratorAccuracy);
40 Optional<Long> bucketCountEstimate = estimateCardinality.apply(groupMask, denominatorAccuracy);
41
42 return totalCountEstimate.flatMap(matchCount ->
43 bucketCountEstimate.map(bucketCount ->
44 bucketCount == 0L ? 0L : ((double) matchCount) / bucketCount
45 ));
46 }
47
48 public static Optional<Double> min(Optional<Double> a, Optional<Double> b) {
49 if (b.isPresent()) {
50 if (a.isPresent()) {
51 return Optional.of(Math.min(a.get(), b.get()));
52 } else return b;
53 } else return a;
54 }
55 public static Optional<Double> min(Optional<Double> a, double b) {
56 if (a.isPresent()) {
57 return Optional.of(Math.min(a.get(), b));
58 } else return Optional.of(b);
59 }
60
61
62}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java
new file mode 100644
index 00000000..926a591f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java
@@ -0,0 +1,217 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.planning.helpers;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.LinkedList;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Queue;
19import java.util.Set;
20import java.util.stream.Collectors;
21
22import tools.refinery.viatra.runtime.matchers.context.IInputKey;
23import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
24import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint;
25import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
26import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
27import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
28
29/**
30 * @author Gabor Bergmann
31 * @author Tamas Szabo
32 */
33public class TypeHelper {
34
35 private TypeHelper() {
36 // Hiding constructor for utility class
37 }
38
39 /**
40 * Collects the type constraints for the specified collection of variables. The type constraints consist of the
41 * constraints directly enforced on the variable itself, plus all of those that the given variable is unified with
42 * through equalities.
43 *
44 * @param variables
45 * the variables in question
46 * @param constraints
47 * the constraints in the pattern body
48 * @param context
49 * the query meta context
50 * @return the mapping from variable to set of type constraints
51 * @since 1.6
52 */
53 public static Map<PVariable, Set<IInputKey>> inferUnaryTypesFor(Iterable<PVariable> variables,
54 Set<PConstraint> constraints, IQueryMetaContext context) {
55 Map<PVariable, Set<TypeJudgement>> typeMap = TypeHelper.inferUnaryTypes(constraints, context);
56 return inferUnaryTypesFor(variables, typeMap);
57 }
58
59 /**
60 * Collects the type constraints for the specified collection of variables. The type constraints consist of the
61 * constraints directly enforced on the variable itself, plus all of those that the given variable is unified with
62 * through equalities.
63 *
64 * The method accepts a type map which is the result of the basic type inference from the
65 * {@link TypeHelper.inferUnaryTypes} method. The purpose of this method is that the type map can be reused across
66 * several calls to this method.
67 *
68 * @param variables
69 * the variables in question
70 * @param typeMap
71 * the type map of inference results
72 * @return the mapping from variable to set of type constraints
73 * @since 1.6
74 */
75 public static Map<PVariable, Set<IInputKey>> inferUnaryTypesFor(Iterable<PVariable> variables,
76 Map<PVariable, Set<TypeJudgement>> typeMap) {
77 Map<PVariable, Set<IInputKey>> result = new HashMap<PVariable, Set<IInputKey>>();
78
79 for (PVariable original : variables) {
80 // it can happen that the variable was unified into an other one due to equalities
81 Set<IInputKey> keys = new HashSet<IInputKey>();
82 PVariable current = original;
83
84 while (current != null) {
85 Set<TypeJudgement> judgements = typeMap.get(current);
86 if (judgements != null) {
87 for (TypeJudgement judgement : judgements) {
88 keys.add(judgement.getInputKey());
89 }
90 }
91 current = current.getDirectUnifiedInto();
92 }
93
94 result.put(original, keys);
95 }
96
97 return result;
98 }
99
100 /**
101 * Infers unary type information for variables, based on the given constraints.
102 *
103 * Subsumptions are not taken into account.
104 *
105 * @param constraints
106 * the set of constraints to extract type info from
107 */
108 public static Map<PVariable, Set<TypeJudgement>> inferUnaryTypes(Set<PConstraint> constraints,
109 IQueryMetaContext context) {
110 Set<TypeJudgement> equivalentJudgements = getDirectJudgements(constraints, context);
111 Set<TypeJudgement> impliedJudgements = typeClosure(equivalentJudgements, context);
112
113 Map<PVariable, Set<TypeJudgement>> results = new HashMap<PVariable, Set<TypeJudgement>>();
114 for (TypeJudgement typeJudgement : impliedJudgements) {
115 final IInputKey inputKey = typeJudgement.getInputKey();
116 if (inputKey.getArity() == 1) {
117 PVariable variable = (PVariable) typeJudgement.getVariablesTuple().get(0);
118 Set<TypeJudgement> inferredTypes = results.computeIfAbsent(variable, v -> new HashSet<>());
119 inferredTypes.add(typeJudgement);
120 }
121 }
122 return results;
123 }
124
125 /**
126 * Gets direct judgements reported by constraints. No closure is applied yet.
127 */
128 public static Set<TypeJudgement> getDirectJudgements(Set<PConstraint> constraints, IQueryMetaContext context) {
129 Set<TypeJudgement> equivalentJudgements = new HashSet<TypeJudgement>();
130 for (PConstraint pConstraint : constraints) {
131 if (pConstraint instanceof ITypeInfoProviderConstraint) {
132 equivalentJudgements.addAll(((ITypeInfoProviderConstraint) pConstraint).getImpliedJudgements(context));
133 }
134 }
135 return equivalentJudgements;
136 }
137
138 /**
139 * Calculates the closure of a set of type judgements, with respect to supertyping.
140 *
141 * @return the set of all type judgements in typesToClose and all their direct and indirect supertypes
142 */
143 public static Set<TypeJudgement> typeClosure(Set<TypeJudgement> typesToClose, IQueryMetaContext context) {
144 return typeClosure(Collections.<TypeJudgement> emptySet(), typesToClose, context);
145 }
146
147 /**
148 * Calculates the closure of a set of type judgements (with respect to supertyping), where the closure has been
149 * calculated before for a given base set, but not for a separate delta set.
150 * <p>
151 * Precondition: the set (typesToClose MINUS delta) is already closed w.r.t. supertyping.
152 *
153 * @return the set of all type judgements in typesToClose and all their direct and indirect supertypes
154 * @since 1.6
155 */
156 public static Set<TypeJudgement> typeClosure(Set<TypeJudgement> preclosedBaseSet, Set<TypeJudgement> delta,
157 IQueryMetaContext context) {
158 Queue<TypeJudgement> queue = delta.stream().filter(input -> !preclosedBaseSet.contains(input)).collect(Collectors.toCollection(LinkedList::new));
159 if (queue.isEmpty())
160 return preclosedBaseSet;
161
162 Set<TypeJudgement> closure = new HashSet<TypeJudgement>(preclosedBaseSet);
163
164 Map<TypeJudgement, Set<TypeJudgement>> conditionalImplications = new HashMap<>();
165 for (TypeJudgement typeJudgement : closure) {
166 conditionalImplications.putAll(typeJudgement.getConditionalImpliedJudgements(context));
167 }
168
169 do {
170 TypeJudgement deltaType = queue.poll();
171 if (closure.add(deltaType)) {
172 // direct implications
173 queue.addAll(deltaType.getDirectlyImpliedJudgements(context));
174
175 // conditional implications, source key processed before, this is the condition key
176 final Set<TypeJudgement> implicationSet = conditionalImplications.get(deltaType);
177 if (implicationSet != null) {
178 queue.addAll(implicationSet);
179 }
180
181 // conditional implications, this is the source key
182 Map<TypeJudgement, Set<TypeJudgement>> deltaConditionalImplications = deltaType
183 .getConditionalImpliedJudgements(context);
184 for (Entry<TypeJudgement, Set<TypeJudgement>> entry : deltaConditionalImplications.entrySet()) {
185 if (closure.contains(entry.getKey())) {
186 // condition processed before
187 queue.addAll(entry.getValue());
188 } else {
189 // condition not processed yet
190 conditionalImplications.computeIfAbsent(entry.getKey(), key -> new HashSet<>())
191 .addAll(entry.getValue());
192 }
193 }
194 }
195 } while (!queue.isEmpty());
196
197 return closure;
198 }
199
200 /**
201 * Calculates a remainder set of types from a larger set, that are not subsumed by a given set of subsuming types.
202 *
203 * @param subsumableTypes
204 * a set of types from which some may be implied by the subsuming types
205 * @param subsumingTypes
206 * a set of types that may imply some of the subsuming types
207 * @return the collection of types in subsumableTypes that are NOT identical to or supertypes of any type in
208 * subsumingTypes.
209 */
210 public static Set<TypeJudgement> subsumeTypes(Set<TypeJudgement> subsumableTypes, Set<TypeJudgement> subsumingTypes,
211 IQueryMetaContext context) {
212 Set<TypeJudgement> closure = typeClosure(subsumingTypes, context);
213 Set<TypeJudgement> subsumed = new HashSet<TypeJudgement>(subsumableTypes);
214 subsumed.removeAll(closure);
215 return subsumed;
216 }
217}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java
new file mode 100644
index 00000000..2c285b54
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Collections;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16import tools.refinery.viatra.runtime.matchers.util.Preconditions;
17
18/**
19 * Represents a constraint application on a single parent SubPlan.
20 * <p> Either a "selection" filter operation according to a deferred PConstraint (or transform in case of eval/aggregate), or
21 * alternatively a shorthand for PJoin + a PEnumerate on the right input for an enumerable PConstraint.
22 *
23 * <p> <b>WARNING</b>: if there are coinciding variables in the variable tuple of the enumerable constraint,
24 * it is the responsibility of the compiler to check them for equality.
25 *
26 * @author Bergmann Gabor
27 *
28 */
29public class PApply extends POperation {
30
31 private PConstraint pConstraint;
32
33 public PApply(PConstraint pConstraint) {
34 super();
35 this.pConstraint = pConstraint;
36 }
37 public PConstraint getPConstraint() {
38 return pConstraint;
39 }
40
41 @Override
42 public String getShortName() {
43 return String.format("APPLY_%s", pConstraint.toString());
44 }
45
46 @Override
47 public Set<? extends PConstraint> getDeltaConstraints() {
48 return Collections.singleton(pConstraint);
49 }
50
51 @Override
52 public int numParentSubPlans() {
53 return 1;
54 }
55
56 @Override
57 public void checkConsistency(SubPlan subPlan) {
58 super.checkConsistency(subPlan);
59 for (SubPlan parentPlan : subPlan.getParentPlans())
60 Preconditions.checkArgument(!parentPlan.getAllEnforcedConstraints().contains(pConstraint),
61 "Double-checking constraint %s", pConstraint);
62 // TODO obtain context?
63 //if (pConstraint instanceof DeferredPConstraint)
64 // Preconditions.checkArgument(((DeferredPConstraint) pConstraint).isReadyAt(subPlan, context))
65 }
66
67 @Override
68 public int hashCode() {
69 final int prime = 31;
70 int result = 1;
71 result = prime
72 * result
73 + ((pConstraint == null) ? 0 : pConstraint
74 .hashCode());
75 return result;
76 }
77 @Override
78 public boolean equals(Object obj) {
79 if (this == obj)
80 return true;
81 if (obj == null)
82 return false;
83 if (!(obj instanceof PApply))
84 return false;
85 PApply other = (PApply) obj;
86 if (pConstraint == null) {
87 if (other.pConstraint != null)
88 return false;
89 } else if (!pConstraint.equals(other.pConstraint))
90 return false;
91 return true;
92 }
93
94}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java
new file mode 100644
index 00000000..a975d50c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Collections;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16
17/**
18 * Represents a base relation defined by the instance set of an enumerable PConstraint; there are no parent SubPlans.
19 *
20 * <p> <b>WARNING</b>: if there are coinciding variables in the variable tuple of the enumerable constraint,
21 * it is the responsibility of the compiler to check them for equality.
22 * @author Bergmann Gabor
23 *
24 */
25public class PEnumerate extends POperation {
26
27 EnumerablePConstraint enumerablePConstraint;
28
29 public PEnumerate(EnumerablePConstraint enumerablePConstraint) {
30 super();
31 this.enumerablePConstraint = enumerablePConstraint;
32 }
33 public EnumerablePConstraint getEnumerablePConstraint() {
34 return enumerablePConstraint;
35 }
36
37 @Override
38 public Set<? extends PConstraint> getDeltaConstraints() {
39 return Collections.singleton(enumerablePConstraint);
40 }
41 @Override
42 public int numParentSubPlans() {
43 return 0;
44 }
45 @Override
46 public String getShortName() {
47 return enumerablePConstraint.toString();
48 }
49 @Override
50 public int hashCode() {
51 final int prime = 31;
52 int result = 1;
53 result = prime
54 * result
55 + ((enumerablePConstraint == null) ? 0 : enumerablePConstraint
56 .hashCode());
57 return result;
58 }
59 @Override
60 public boolean equals(Object obj) {
61 if (this == obj)
62 return true;
63 if (obj == null)
64 return false;
65 if (!(obj instanceof PEnumerate))
66 return false;
67 PEnumerate other = (PEnumerate) obj;
68 if (enumerablePConstraint == null) {
69 if (other.enumerablePConstraint != null)
70 return false;
71 } else if (!enumerablePConstraint.equals(other.enumerablePConstraint))
72 return false;
73 return true;
74 }
75
76}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java
new file mode 100644
index 00000000..10e0a85a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Collections;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
15
16/**
17 * Represents a natural join of two parent SubPlans.
18 * @author Bergmann Gabor
19 *
20 */
21public class PJoin extends POperation {
22
23// // TODO leave here? is this a problem in equivalnece checking?
24// private Set<PVariable> onVariables;
25
26 public PJoin(/*Set<PVariable> onVariables*/) {
27 super();
28 //this.onVariables = new HashSet<PVariable>(onVariables);
29 }
30// public Set<PVariable> getOnVariables() {
31// return onVariables;
32// }
33
34 @Override
35 public Set<? extends PConstraint> getDeltaConstraints() {
36 return Collections.emptySet();
37 }
38 @Override
39 public int numParentSubPlans() {
40 return 2;
41 }
42
43 @Override
44 public String getShortName() {
45 return "JOIN"; //String.format("JOIN_{%s}", Joiner.on(",").join(onVariables));
46 }
47
48 @Override
49 public int hashCode() {
50 return getClass().hashCode();
51 }
52 @Override
53 public boolean equals(Object obj) {
54 if (this == obj)
55 return true;
56 if (obj == null)
57 return false;
58 if (!(obj instanceof PJoin))
59 return false;
60 return true;
61 }
62
63
64}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java
new file mode 100644
index 00000000..e71cf217
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java
@@ -0,0 +1,52 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Set;
12
13import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
14import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
15import tools.refinery.viatra.runtime.matchers.util.Preconditions;
16
17/**
18 * Abstract superclass for representing a high-level query evaluation operation.
19 *
20 * <p> Subclasses correspond to various POperations modeled after relational algebra.
21 *
22 * @author Bergmann Gabor
23 *
24 */
25public abstract class POperation {
26
27 /**
28 * Newly enforced constraints
29 */
30 public abstract Set<? extends PConstraint> getDeltaConstraints();
31
32 public abstract String getShortName();
33
34 /**
35 * @return the number of SubPlans that must be specified as parents
36 */
37 public abstract int numParentSubPlans();
38
39 /**
40 * Checks whether this constraint can be properly applied at the given SubPlan.
41 */
42 public void checkConsistency(SubPlan subPlan) {
43 Preconditions.checkArgument(this == subPlan.getOperation(), "POperation misalignment");
44 Preconditions.checkArgument(subPlan.getParentPlans().size() == numParentSubPlans(), "Incorrect number of parent SubPlans");
45 }
46
47 @Override
48 public String toString() {
49 return getShortName();
50 }
51
52}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java
new file mode 100644
index 00000000..d0539b2c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java
@@ -0,0 +1,109 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.List;
14import java.util.Set;
15import java.util.stream.Collectors;
16
17import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
18import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
20import tools.refinery.viatra.runtime.matchers.util.Preconditions;
21
22/**
23 * Represents a projection of a single parent SubPlan onto a limited set of variables.
24 * <p> May optionally prescribe an ordering of variables (List, as opposed to Set).
25 *
26 * @author Bergmann Gabor
27 *
28 */
29public class PProject extends POperation {
30
31 private Collection<PVariable> toVariables;
32 private boolean ordered;
33
34
35 public PProject(Set<PVariable> toVariables) {
36 super();
37 this.toVariables = toVariables;
38 this.ordered = false;
39 }
40 public PProject(List<PVariable> toVariables) {
41 super();
42 this.toVariables = toVariables;
43 this.ordered = true;
44 }
45
46 public Collection<PVariable> getToVariables() {
47 return toVariables;
48 }
49 public boolean isOrdered() {
50 return ordered;
51 }
52
53 @Override
54 public Set<? extends PConstraint> getDeltaConstraints() {
55 return Collections.emptySet();
56 }
57 @Override
58 public int numParentSubPlans() {
59 return 1;
60 }
61 @Override
62 public void checkConsistency(SubPlan subPlan) {
63 super.checkConsistency(subPlan);
64 final SubPlan parentPlan = subPlan.getParentPlans().get(0);
65
66 Preconditions.checkArgument(parentPlan.getVisibleVariables().containsAll(toVariables),
67 () -> toVariables.stream()
68 .filter(input -> !parentPlan.getVisibleVariables().contains(input)).map(PVariable::getName)
69 .collect(Collectors.joining(",", "Variables missing from project: ", "")));
70 }
71
72 @Override
73 public String getShortName() {
74 return String.format("PROJECT%s_{%s}", ordered? "!" : "",
75 toVariables.stream().map(PVariable::getName).collect(Collectors.joining(",")));
76 }
77
78 @Override
79 public int hashCode() {
80 final int prime = 31;
81 int result = 1;
82 result = prime * result + (ordered ? 1231 : 1237);
83 result = prime * result
84 + ((toVariables == null) ? 0 : toVariables.hashCode());
85 return result;
86 }
87 @Override
88 public boolean equals(Object obj) {
89 if (this == obj)
90 return true;
91 if (obj == null)
92 return false;
93 if (!(obj instanceof PProject))
94 return false;
95 PProject other = (PProject) obj;
96 if (ordered != other.ordered)
97 return false;
98 if (toVariables == null) {
99 if (other.toVariables != null)
100 return false;
101 } else if (!toVariables.equals(other.toVariables))
102 return false;
103 return true;
104 }
105
106
107
108
109}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java
new file mode 100644
index 00000000..9e6ea10e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java
@@ -0,0 +1,90 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.planning.operations;
10
11import java.util.Arrays;
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.Set;
15import java.util.stream.Collectors;
16
17import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
18import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
19
20/**
21 * No constraints, and no parent SubPlan, just a (possibly empty) set of a priori known (input) variables. Satisfied by a single tuple.
22 *
23 * <p> Can also be used without a priori variables,
24 * e.g. as a "virtual parent" in extreme cases,
25 * such as <code>pattern foo(Bar) = {Bar = eval (3*4)} </code>
26 *
27 * @author Bergmann Gabor
28 *
29 */
30public class PStart extends POperation {
31
32 private Set<PVariable> aPrioriVariables;
33
34
35 public PStart(Set<PVariable> aPrioriVariables) {
36 super();
37 this.aPrioriVariables = aPrioriVariables;
38 }
39 public PStart(PVariable... aPrioriVariables) {
40 this(new HashSet<PVariable>(Arrays.asList(aPrioriVariables)));
41 }
42 public Set<PVariable> getAPrioriVariables() {
43 return aPrioriVariables;
44 }
45
46 @Override
47 public String getShortName() {
48 return aPrioriVariables.stream().map(PVariable::getName).collect(Collectors.joining(",", "START_{", "}"));
49 }
50 @Override
51 public int numParentSubPlans() {
52 return 0;
53 }
54
55 @Override
56 public Set<? extends PConstraint> getDeltaConstraints() {
57 return Collections.emptySet();
58 }
59
60 @Override
61 public int hashCode() {
62 final int prime = 31;
63 int result = 1;
64 result = prime
65 * result
66 + ((aPrioriVariables == null) ? 0 : aPrioriVariables.hashCode());
67 return result;
68 }
69
70 @Override
71 public boolean equals(Object obj) {
72 if (this == obj)
73 return true;
74 if (obj == null)
75 return false;
76 if (!(obj instanceof PStart))
77 return false;
78 PStart other = (PStart) obj;
79 if (aPrioriVariables == null) {
80 if (other.aPrioriVariables != null)
81 return false;
82 } else if (!aPrioriVariables.equals(other.aPrioriVariables))
83 return false;
84 return true;
85 }
86
87
88
89
90}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java
new file mode 100644
index 00000000..eda4aa25
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java
@@ -0,0 +1,108 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
13
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.Map;
17import java.util.Set;
18import java.util.concurrent.atomic.AtomicInteger;
19
20/**
21 * @author Gabor Bergmann
22 *
23 */
24public abstract class BasePConstraint implements PConstraint {
25
26
27 protected PBody pBody;
28 private final Set<PVariable> affectedVariables;
29
30
31 private final int sequentialID = nextID.getAndIncrement();
32
33 // Use a static atomic integer to avoid race conditions when creating new constraints.
34 private static AtomicInteger nextID = new AtomicInteger(0);
35
36 public BasePConstraint(PBody pBody, Set<PVariable> affectedVariables) {
37 super();
38 this.pBody = pBody;
39 this.affectedVariables = new HashSet<PVariable>(affectedVariables);
40
41 for (PVariable pVariable : affectedVariables) {
42 pVariable.refer(this);
43 }
44 pBody.registerConstraint(this);
45 }
46
47 @Override
48 public String toString() {
49 return "PC[" + getClass().getSimpleName() + ":" + toStringRest() + "]";
50 }
51
52 protected abstract String toStringRest();
53
54 @Override
55 public Set<PVariable> getAffectedVariables() {
56 return affectedVariables;
57 }
58
59 @Override
60 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
61 return Collections.emptyMap();
62 }
63
64 @Override
65 public void replaceVariable(PVariable obsolete, PVariable replacement) {
66 pBody.checkMutability();
67 if (affectedVariables.remove(obsolete)) {
68 affectedVariables.add(replacement);
69 obsolete.unrefer(this);
70 replacement.refer(this);
71 doReplaceVariable(obsolete, replacement);
72 }
73 }
74
75 protected abstract void doReplaceVariable(PVariable obsolete, PVariable replacement);
76
77 @Override
78 public void delete() {
79 pBody.checkMutability();
80 for (PVariable pVariable : affectedVariables) {
81 pVariable.unrefer(this);
82 }
83 pBody.unregisterConstraint(this);
84 }
85
86 @Override
87 public void checkSanity() {
88 }
89
90 /**
91 * For backwards compatibility. Equivalent to {@link #getBody()}
92 */
93 public PBody getPSystem() {
94 return pBody;
95 }
96 /**
97 * @since 2.1
98 */
99 @Override
100 public PBody getBody() {
101 return pBody;
102 }
103
104 @Override
105 public int getMonotonousID() {
106 return sequentialID;
107 }
108}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java
new file mode 100644
index 00000000..d2bf088c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
15import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
16
17/**
18 * Any constraint that can only be checked on certain SubPlans (e.g. those plans that already contain some variables).
19 *
20 * @author Gabor Bergmann
21 *
22 */
23public abstract class DeferredPConstraint extends BasePConstraint {
24
25 public DeferredPConstraint(PBody pBody, Set<PVariable> affectedVariables) {
26 super(pBody, affectedVariables);
27 }
28
29 public abstract boolean isReadyAt(SubPlan plan, IQueryMetaContext context);
30
31}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java
new file mode 100644
index 00000000..9129aa47
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java
@@ -0,0 +1,59 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15
16/**
17 * A constraint for which all satisfying tuples of variable values can be enumerated at any point during run-time.
18 *
19 * @author Gabor Bergmann
20 *
21 */
22public abstract class EnumerablePConstraint extends BasePConstraint {
23 protected Tuple variablesTuple;
24
25 protected EnumerablePConstraint(PBody pBody, Tuple variablesTuple) {
26 super(pBody, variablesTuple.<PVariable> getDistinctElements());
27 this.variablesTuple = variablesTuple;
28 }
29
30 @Override
31 public void doReplaceVariable(PVariable obsolete, PVariable replacement) {
32 variablesTuple = variablesTuple.replaceAll(obsolete, replacement);
33 }
34
35 @Override
36 protected String toStringRest() {
37 String stringRestRest = toStringRestRest();
38 String tupleString = "@" + variablesTuple.toString();
39 return stringRestRest == null ? tupleString : ":" + stringRestRest + tupleString;
40 }
41
42 protected String toStringRestRest() {
43 return null;
44 }
45
46 public Tuple getVariablesTuple() {
47 return variablesTuple;
48 }
49
50 @Override
51 public Set<PVariable> getDeducedVariables() {
52 return getAffectedVariables();
53 }
54
55 public PVariable getVariableInTuple(int index) {
56 return (PVariable) this.variablesTuple.get(index);
57 }
58
59}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java
new file mode 100644
index 00000000..686999f7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java
@@ -0,0 +1,42 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11/**
12 * An expression evaluator is used to execute arbitrary Java code during pattern matching. In order to include the
13 * evaluation in the planning seemlessly it is expected from the evaluator implementors to report all used PVariables by
14 * name.
15 *
16 * @author Zoltan Ujhelyi
17 *
18 */
19public interface IExpressionEvaluator {
20
21 /**
22 * A textual description of the expression. Used only for debug purposes, but must not be null.
23 */
24 String getShortDescription();
25
26 /**
27 * All input parameter names should be reported correctly.
28 */
29 Iterable<String> getInputParameterNames();
30
31 /**
32 * The expression evaluator code
33 *
34 * @param provider
35 * the value provider is an engine-specific way of reading internal variable tuples to evaluate the
36 * expression with
37 * @return the result of the expression: in case of predicate evaluation the return value must be true or false;
38 * otherwise the result can be an arbitrary object. No null values should be returned.
39 * @throws Exception
40 */
41 Object evaluateExpression(IValueProvider provider) throws Exception;
42}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java
new file mode 100644
index 00000000..8f647c64
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2022, Tamas Szabo, GitHub
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.Collection;
12
13import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
14
15/**
16 * A {@link PConstraint} that implements this interface refers to a list of PQueries.
17 *
18 * @author Tamas Szabo
19 * @since 2.8
20 *
21 */
22public interface IMultiQueryReference {
23
24 Collection<PQuery> getReferredQueries();
25
26}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java
new file mode 100644
index 00000000..9ee05b39
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.Collections;
12import java.util.List;
13
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15
16/**
17 * A {@link PConstraint} that implements this interface refers to a {@link PQuery}.
18 *
19 * @author Zoltan Ujhelyi
20 *
21 */
22public interface IQueryReference extends IMultiQueryReference {
23
24 PQuery getReferredQuery();
25
26 /**
27 * @since 2.8
28 */
29 @Override
30 default List<PQuery> getReferredQueries() {
31 return Collections.singletonList(getReferredQuery());
32 }
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java
new file mode 100644
index 00000000..e4c396d8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java
@@ -0,0 +1,47 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2022, Tamas Szabo, GitHub
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.List;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15
16/**
17 * Implementations of this interface take an arbitrary number of input relations with their contents and compute the
18 * tuples of a single output relation.
19 *
20 * @author Tamas Szabo
21 * @since 2.8
22 *
23 */
24public interface IRelationEvaluator {
25
26 /**
27 * A textual description of the evaluator. Used only for debug purposes, but must not be null.
28 */
29 String getShortDescription();
30
31 /**
32 * The relation evaluator code. For performance reasons, it is expected that the returned set is a mutable
33 * collection, and the caller must be allowed to actually perform mutations!
34 */
35 Set<Tuple> evaluateRelation(List<Set<Tuple>> inputs) throws Exception;
36
37 /**
38 * For each input relation that this evaluator requires, this method returns the expected arities of the relations in order.
39 */
40 List<Integer> getInputArities();
41
42 /**
43 * Returns the arity of the output relation that this evaluator computes.
44 */
45 int getOutputArity();
46
47}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java
new file mode 100644
index 00000000..b72035a8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.Map;
14import java.util.Map.Entry;
15
16import tools.refinery.viatra.runtime.matchers.context.IInputKey;
17import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19
20import java.util.Set;
21
22/**
23 * Common superinterface of enumerable and deferred type constraints.
24 * @author Bergmann Gabor
25 *
26 */
27public interface ITypeConstraint extends ITypeInfoProviderConstraint {
28
29 public abstract TypeJudgement getEquivalentJudgement();
30
31 /**
32 * Static internal utility class for implementations of {@link ITypeConstraint}s.
33 * @author Bergmann Gabor
34 */
35 public static class TypeConstraintUtil {
36
37 private TypeConstraintUtil() {
38 // Hiding constructor for utility class
39 }
40
41 public static Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context, IInputKey inputKey, Tuple variablesTuple) {
42 final Map<Set<PVariable>, Set<PVariable>> result = new HashMap<Set<PVariable>, Set<PVariable>>();
43
44 Set<Entry<Set<Integer>, Set<Integer>>> dependencies = context.getFunctionalDependencies(inputKey).entrySet();
45 for (Entry<Set<Integer>, Set<Integer>> dependency : dependencies) {
46 result.put(
47 transcribeVariables(dependency.getKey(), variablesTuple),
48 transcribeVariables(dependency.getValue(), variablesTuple)
49 );
50 }
51
52 return result;
53 }
54
55 private static Set<PVariable> transcribeVariables(Set<Integer> indices, Tuple variablesTuple) {
56 Set<PVariable> result = new HashSet<PVariable>();
57 for (Integer index : indices) {
58 result.add((PVariable) variablesTuple.get(index));
59 }
60 return result;
61 }
62
63 }
64
65}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java
new file mode 100644
index 00000000..ff127d38
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
15
16/**
17 * @author Gabor Bergmann
18 *
19 */
20public interface ITypeInfoProviderConstraint extends PConstraint {
21
22 /**
23 * Returns type information implied by this constraint.
24 *
25 */
26 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context);
27
28}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java
new file mode 100644
index 00000000..d959adc4
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11/**
12 * Helper interface to get values from a tuple of variables. All pattern matching engines are expected to implement this
13 * to handle their internal structures.
14 *
15 * @author Zoltan Ujhelyi
16 *
17 */
18public interface IValueProvider {
19
20 /**
21 * Returns the value of the selected variable
22 * @param variableName
23 * @return the value of the variable; never null
24 * @throws IllegalArgumentException if the variable is not defined
25 */
26 Object getValue(String variableName);
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java
new file mode 100644
index 00000000..a82d12ec
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.Set;
12
13import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
14import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PProblem;
16import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
17
18/**
19 * Adds extra methods to the PQuery interface to initialize its contents.
20 *
21 * @author Zoltan Ujhelyi
22 *
23 */
24public interface InitializablePQuery extends PQuery {
25
26 /**
27 * Sets the query status. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED uninitialized}.
28 *
29 * @param status the new status
30 */
31 void setStatus(PQueryStatus status);
32
33 /**
34 * Adds a detected error. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED uninitialized}.
35 *
36 * @param problem the new problem
37 */
38 void addError(PProblem problem);
39
40 /**
41 * Sets up the bodies of the pattern. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED
42 * uninitialized}.
43 *
44 * @param bodies
45 * @throws ViatraQueryRuntimeException
46 */
47 void initializeBodies(Set<PBody> bodies);
48
49 /**
50 * Adds an annotation to the specification. Only applicable if the pattern is still
51 * {@link PQueryStatus#UNINITIALIZED uninitialized}.
52 *
53 * @param annotation
54 */
55 void addAnnotation(PAnnotation annotation);
56}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java
new file mode 100644
index 00000000..91eea817
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java
@@ -0,0 +1,39 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
13
14/**
15 * @author Gabor Bergmann
16 *
17 */
18public abstract class KeyedEnumerablePConstraint<KeyType> extends EnumerablePConstraint {
19
20 protected KeyType supplierKey;
21
22 public KeyedEnumerablePConstraint(PBody pBody, Tuple variablesTuple,
23 KeyType supplierKey) {
24 super(pBody, variablesTuple);
25 this.supplierKey = supplierKey;
26 }
27
28 @Override
29 protected String toStringRestRest() {
30 return supplierKey == null ? "$any(null)" : keyToString();
31 }
32
33 protected abstract String keyToString();
34
35 public KeyType getSupplierKey() {
36 return supplierKey;
37 }
38
39}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java
new file mode 100644
index 00000000..c38dc23a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java
@@ -0,0 +1,289 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.ArrayList;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.LinkedHashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.WeakHashMap;
20import java.util.stream.Collectors;
21
22import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
23import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper;
24import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
25import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue;
26import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
27import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
28import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
29import tools.refinery.viatra.runtime.matchers.util.Preconditions;
30
31/**
32 * A set of constraints representing a pattern body
33 *
34 * @author Gabor Bergmann
35 *
36 */
37public class PBody implements PTraceable {
38
39 public static final String VIRTUAL_VARIABLE_PREFIX = ".virtual";
40 private static final String VIRTUAL_VARIABLE_PATTERN = VIRTUAL_VARIABLE_PREFIX + "{%d}";
41
42 private PQuery query;
43
44 /**
45 * If null, then parent query status is reused
46 */
47 private PQueryStatus status = PQueryStatus.UNINITIALIZED;
48
49 private Set<PVariable> allVariables;
50 private Set<PVariable> uniqueVariables;
51 private List<ExportedParameter> symbolicParameters;
52 private Map<Object, PVariable> variablesByName;
53 private Set<PConstraint> constraints;
54 private int nextVirtualNodeID;
55 private PDisjunction containerDisjunction;
56
57 public PBody(PQuery query) {
58 super();
59 this.query = query;
60 allVariables = new LinkedHashSet<>();
61 uniqueVariables = new LinkedHashSet<>();
62 variablesByName = new HashMap<>();
63 constraints = new LinkedHashSet<>();
64 }
65
66 /**
67 * @return whether the submission of the new variable was successful
68 */
69 private boolean addVariable(PVariable var) {
70 checkMutability();
71 Object name = var.getName();
72 if (!variablesByName.containsKey(name)) {
73 allVariables.add(var);
74 if (var.isUnique())
75 uniqueVariables.add(var);
76 variablesByName.put(name, var);
77 return true;
78 } else {
79 return false;
80 }
81 }
82
83 /**
84 * Use this method to add a newly created constraint to the pSystem.
85 *
86 * @return whether the submission of the new constraint was successful
87 */
88 boolean registerConstraint(PConstraint constraint) {
89 checkMutability();
90 return constraints.add(constraint);
91 }
92
93 /**
94 * Use this method to remove an obsolete constraint from the pSystem.
95 *
96 * @return whether the removal of the constraint was successful
97 */
98 boolean unregisterConstraint(PConstraint constraint) {
99 checkMutability();
100 return constraints.remove(constraint);
101 }
102
103 @SuppressWarnings("unchecked")
104 public <ConstraintType> Set<ConstraintType> getConstraintsOfType(Class<ConstraintType> constraintClass) {
105 Set<ConstraintType> result = new HashSet<ConstraintType>();
106 for (PConstraint pConstraint : constraints) {
107 if (constraintClass.isInstance(pConstraint))
108 result.add((ConstraintType) pConstraint);
109 }
110 return result;
111 }
112
113 public PVariable newVirtualVariable() {
114 checkMutability();
115 String name;
116 do {
117
118 name = String.format(VIRTUAL_VARIABLE_PATTERN, nextVirtualNodeID++);
119 } while (variablesByName.containsKey(name));
120 PVariable var = new PVariable(this, name, true);
121 addVariable(var);
122 return var;
123 }
124
125 public PVariable newVirtualVariable(String name) {
126 checkMutability();
127 Preconditions.checkArgument(!variablesByName.containsKey(name), "ID %s already used for a virtual variable", name);
128 PVariable var = new PVariable(this, name, true);
129 addVariable(var);
130 return var;
131 }
132
133 public PVariable newConstantVariable(Object value) {
134 checkMutability();
135 PVariable virtual = newVirtualVariable();
136 new ConstantValue(this, virtual, value);
137 return virtual;
138 }
139
140 public Set<PVariable> getAllVariables() {
141 return allVariables;
142 }
143
144 public Set<PVariable> getUniqueVariables() {
145 return uniqueVariables;
146 }
147
148 private PVariable getVariableByName(Object name) {
149 return variablesByName.get(name).getUnifiedIntoRoot();
150 }
151
152 /**
153 * Find a PVariable by name
154 *
155 * @param name
156 * @return the found variable
157 * @throws IllegalArgumentException
158 * if no PVariable is found with the selected name
159 */
160 public PVariable getVariableByNameChecked(Object name) {
161 if (!variablesByName.containsKey(name))
162 throw new IllegalArgumentException(String.format("Cannot find PVariable %s", name));
163 return getVariableByName(name);
164 }
165
166 /**
167 * Finds and returns a PVariable by name. If no PVariable exists with the name in the body, a new one is created. If
168 * the name of the variable starts with {@value #VIRTUAL_VARIABLE_PREFIX}, the created variable will be considered
169 * virtual.
170 *
171 * @param name
172 * @return a PVariable with the selected name; never null
173 */
174 public PVariable getOrCreateVariableByName(String name) {
175 checkMutability();
176 if (!variablesByName.containsKey(name)) {
177 addVariable(new PVariable(this, name, name.startsWith(VIRTUAL_VARIABLE_PREFIX)));
178 }
179 return getVariableByName(name);
180 }
181
182 public Set<PConstraint> getConstraints() {
183 return constraints;
184 }
185
186 public PQuery getPattern() {
187 return query;
188 }
189
190 void noLongerUnique(PVariable pVariable) {
191 assert (!pVariable.isUnique());
192 uniqueVariables.remove(pVariable);
193 }
194
195 /**
196 * Returns the symbolic parameters of the body. </p>
197 *
198 * <p>
199 * <strong>Warning</strong>: if two PVariables are unified, the returned list changes. If you want to have a stable
200 * version, consider using {@link #getSymbolicParameters()}.
201 *
202 * @return a non-null, but possibly empty list
203 */
204 public List<PVariable> getSymbolicParameterVariables() {
205 return getSymbolicParameters().stream().map(ExportedParameter::getParameterVariable)
206 .collect(Collectors.toList());
207 }
208
209 /**
210 * Returns the exported parameter constraints of the body.
211 *
212 * @return a non-null, but possibly empty list
213 */
214 public List<ExportedParameter> getSymbolicParameters() {
215 if (symbolicParameters == null)
216 symbolicParameters = new ArrayList<>();
217 return symbolicParameters;
218 }
219
220 /**
221 * Sets the exported parameter constraints of the body, if this instance is mutable.
222 * @param symbolicParameters the new value
223 */
224 public void setSymbolicParameters(List<ExportedParameter> symbolicParameters) {
225 checkMutability();
226 this.symbolicParameters = new ArrayList<>(symbolicParameters);
227 }
228
229 /**
230 * Sets a specific status for the body. If set, the parent PQuery status will not be checked; if set to null, its corresponding PQuery
231 * status is checked for mutability.
232 *
233 * @param status
234 * the status to set
235 */
236 public void setStatus(PQueryStatus status) {
237 this.status = status;
238 }
239
240 public boolean isMutable() {
241 if (status == null) {
242 return query.isMutable();
243 } else {
244 return status.equals(PQueryStatus.UNINITIALIZED);
245 }
246 }
247
248 void checkMutability() {
249 if (status == null) {
250 query.checkMutability();
251 } else {
252 Preconditions.checkState(status.equals(PQueryStatus.UNINITIALIZED), "Initialized queries are not mutable");
253 }
254 }
255
256 /**
257 * Returns the disjunction the body is contained with. This disjunction may either be the
258 * {@link PQuery#getDisjunctBodies() canonical disjunction of the corresponding query} or something equivalent.
259 *
260 * @return the container disjunction of the body. Can be null if body is not in a disjunction yet.
261 */
262 public PDisjunction getContainerDisjunction() {
263 return containerDisjunction;
264 }
265
266 /**
267 * @param containerDisjunction the containerDisjunction to set
268 */
269 public void setContainerDisjunction(PDisjunction containerDisjunction) {
270 Preconditions.checkArgument(query.equals(containerDisjunction.getQuery()), "Disjunction of pattern %s incompatible with body %s", containerDisjunction.getQuery().getFullyQualifiedName(), query.getFullyQualifiedName());
271 Preconditions.checkState(this.containerDisjunction == null, "Disjunction is already set.");
272 this.containerDisjunction = containerDisjunction;
273 }
274
275 /**
276 * All unary input keys directly prescribed by constraints, grouped by variable.
277 * <p> to supertype inference or subsumption applied at this point.
278 */
279 public Map<PVariable, Set<TypeJudgement>> getAllUnaryTypeRestrictions(IQueryMetaContext context) {
280 Map<PVariable, Set<TypeJudgement>> currentRestrictions = allUnaryTypeRestrictions.get(context);
281 if (currentRestrictions == null) {
282 currentRestrictions = TypeHelper.inferUnaryTypes(getConstraints(), context);
283 allUnaryTypeRestrictions.put(context, currentRestrictions);
284 }
285 return currentRestrictions;
286 }
287 private WeakHashMap<IQueryMetaContext, Map<PVariable, Set<TypeJudgement>>> allUnaryTypeRestrictions = new WeakHashMap<IQueryMetaContext, Map<PVariable,Set<TypeJudgement>>>();
288
289}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java
new file mode 100644
index 00000000..ae2c4632
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.Comparator;
13import java.util.Map;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
17import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer;
18
19/**
20 * @author Gabor Bergmann
21 *
22 */
23public interface PConstraint extends PTraceable {
24
25 /**
26 * @since 2.1
27 * @return the query body this constraint belongs to
28 */
29 public PBody getBody();
30
31 /**
32 * All variables affected by this constraint.
33 */
34 public Set<PVariable> getAffectedVariables();
35
36 /**
37 * The set of variables whose potential values can be enumerated (once all non-deduced variables have known values).
38 */
39 public Set<PVariable> getDeducedVariables();
40
41 /**
42 * A (preferably minimal) cover of known functional dependencies between variables.
43 * @noreference Use {@link QueryAnalyzer} instead to properly handle dependencies of pattern calls.
44 * @return non-trivial functional dependencies in the form of {variables} --> {variables}, where dependencies with the same lhs are unified.
45 */
46 public Map<Set<PVariable>,Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context);
47
48 public void replaceVariable(PVariable obsolete, PVariable replacement);
49
50 public void delete();
51
52 public void checkSanity();
53
54 /**
55 * Returns an integer ID that is guaranteed to increase strictly monotonously for constraints within a pBody.
56 */
57 public abstract int getMonotonousID();
58
59
60 /**
61 * A comparator that orders constraints by their {@link #getMonotonousID() monotonous identifiers}. Should only used
62 * for tiebreaking in other comparators.
63 *
64 * @since 2.0
65 */
66 public static final Comparator<PConstraint> COMPARE_BY_MONOTONOUS_ID = (arg0, arg1) -> arg0.getMonotonousID() - arg1.getMonotonousID();
67
68
69
70}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java
new file mode 100644
index 00000000..f0241a9c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java
@@ -0,0 +1,16 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Dénes Harmath, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11/**
12 * Marker interface for PSystem elements that can be traced.
13 * @since 1.6
14 */
15public interface PTraceable {
16}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java
new file mode 100644
index 00000000..b6ea4861
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java
@@ -0,0 +1,203 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.HashSet;
13import java.util.Set;
14
15/**
16 * @author Gabor Bergmann
17 *
18 */
19public class PVariable {
20 private PBody pBody;
21 /**
22 * The name of the pattern variable. This is the unique key of the pattern node.
23 */
24 private String name;
25 /**
26 * virtual pVariables are nodes that do not correspond to actual pattern variables; they represent constants or Term
27 * substitutes
28 */
29 private boolean virtual;
30
31 /**
32 * Set of constraints that mention this variable
33 */
34 private Set<PConstraint> referringConstraints;
35
36 /**
37 * Determines whether there are any constraints that can deduce this variable
38 */
39 private Boolean deducable;
40
41 /**
42 * Another PVariable this variable has been unified into. Please use the other variable instead of this. Null iff
43 * this is still a first-class variable.
44 */
45 private PVariable unifiedInto;
46
47 PVariable(PBody pBody, String name) {
48 this(pBody, name, false);
49 }
50
51 PVariable(PBody pBody, String name, boolean virtual) {
52 super();
53 this.pBody = pBody;
54 this.name = name;
55 this.virtual = virtual;
56 // this.exportedParameter = false;
57 this.referringConstraints = new HashSet<PConstraint>();
58 this.unifiedInto = null;
59 this.deducable = false;
60 }
61
62 /**
63 * Replaces this variable with a given other, resulting in their unification. This variable will no longer be
64 * unique.
65 *
66 * @param replacement
67 */
68 public void unifyInto(PVariable replacement) {
69 pBody.checkMutability();
70 replacementCheck();
71 replacement = replacement.getUnifiedIntoRoot();
72
73 if (this.equals(replacement))
74 return;
75
76 if (!this.isVirtual() && replacement.isVirtual()) {
77 replacement.unifyInto(this);
78 } else {
79 // replacement.referringConstraints.addAll(this.referringConstraints);
80 // replacement.exportedParameter |= this.exportedParameter;
81 replacement.virtual &= this.virtual;
82 if (replacement.deducable != null && this.deducable != null)
83 replacement.deducable |= this.deducable;
84 else
85 replacement.deducable = null;
86 Set<PConstraint> snapshotConstraints = // avoid ConcurrentModificationX
87 new HashSet<PConstraint>(this.referringConstraints);
88 for (PConstraint constraint : snapshotConstraints) {
89 constraint.replaceVariable(this, replacement);
90 }
91 // replacementCheck() will fail from this point
92 this.unifiedInto = replacement;
93 pBody.noLongerUnique(this);
94 }
95 }
96
97 /**
98 * Determines whether there are any constraints that can deduce this variable
99 */
100 public boolean isDeducable() {
101 replacementCheck();
102 if (deducable == null) {
103 deducable = false;
104 for (PConstraint pConstraint : getReferringConstraints()) {
105 if (pConstraint.getDeducedVariables().contains(this)) {
106 deducable = true;
107 break;
108 }
109 }
110 }
111 return deducable;
112 }
113
114 /**
115 * Register that this variable is referred by the given constraint.
116 *
117 * @param constraint
118 */
119 public void refer(PConstraint constraint) {
120 pBody.checkMutability();
121 replacementCheck();
122 deducable = null;
123 referringConstraints.add(constraint);
124 }
125
126 /**
127 * Register that this variable is no longer referred by the given constraint.
128 *
129 * @param constraint
130 */
131 public void unrefer(PConstraint constraint) {
132 pBody.checkMutability();
133 replacementCheck();
134 deducable = null;
135 referringConstraints.remove(constraint);
136 }
137
138 /**
139 * @return the name of the pattern variable. This is the unique key of the pattern node.
140 */
141 public String getName() {
142 replacementCheck();
143 return name;
144 }
145
146 /**
147 * @return the virtual
148 */
149 public boolean isVirtual() {
150 replacementCheck();
151 return virtual;
152 }
153
154 /**
155 * @return the referringConstraints
156 */
157 public Set<PConstraint> getReferringConstraints() {
158 replacementCheck();
159 return referringConstraints;
160 }
161
162 @SuppressWarnings("unchecked")
163 public <ConstraintType> Set<ConstraintType> getReferringConstraintsOfType(Class<ConstraintType> constraintClass) {
164 replacementCheck();
165 Set<ConstraintType> result = new HashSet<ConstraintType>();
166 for (PConstraint pConstraint : referringConstraints) {
167 if (constraintClass.isInstance(pConstraint))
168 result.add((ConstraintType) pConstraint);
169 }
170 return result;
171 }
172
173 @Override
174 public String toString() {
175 // replacementCheck();
176 return name;// + ":PatternNode";
177 }
178
179 public PVariable getDirectUnifiedInto() {
180 return unifiedInto;
181 }
182
183 public PVariable getUnifiedIntoRoot() {
184 PVariable nextUnified = unifiedInto;
185 PVariable oldUnifiedInto = this;
186 while (nextUnified != null) {
187 oldUnifiedInto = nextUnified;
188 nextUnified = oldUnifiedInto.getDirectUnifiedInto();
189 }
190 return oldUnifiedInto; // unifiedInto;
191 }
192
193 public boolean isUnique() {
194 return unifiedInto == null;
195 }
196
197 private void replacementCheck() {
198 if (unifiedInto != null)
199 throw new IllegalStateException("Illegal usage of variable " + name + " which has been replaced with "
200 + unifiedInto.name);
201 }
202
203}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java
new file mode 100644
index 00000000..4447b225
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java
@@ -0,0 +1,153 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem;
10
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.stream.Collectors;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.context.IInputKey;
19import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
20import tools.refinery.viatra.runtime.matchers.context.InputKeyImplication;
21import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint;
22import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
25
26/**
27 * A judgement that means that the given tuple of variables will represent a tuple of values that is a member of the extensional relation identified by the given input key.
28 * @author Bergmann Gabor
29 *
30 */
31public class TypeJudgement {
32
33 private IInputKey inputKey;
34 private Tuple variablesTuple;
35 /**
36 * @param inputKey
37 * @param variablesTuple
38 */
39 public TypeJudgement(IInputKey inputKey, Tuple variablesTuple) {
40 super();
41 this.inputKey = inputKey;
42 this.variablesTuple = variablesTuple;
43 }
44 public IInputKey getInputKey() {
45 return inputKey;
46 }
47 public Tuple getVariablesTuple() {
48 return variablesTuple;
49 }
50 @Override
51 public int hashCode() {
52 final int prime = 31;
53 int result = 1;
54 result = prime * result
55 + ((inputKey == null) ? 0 : inputKey.hashCode());
56 result = prime * result
57 + ((variablesTuple == null) ? 0 : variablesTuple.hashCode());
58 return result;
59 }
60 @Override
61 public boolean equals(Object obj) {
62 if (this == obj)
63 return true;
64 if (obj == null)
65 return false;
66 if (!(obj instanceof TypeJudgement))
67 return false;
68 TypeJudgement other = (TypeJudgement) obj;
69 if (inputKey == null) {
70 if (other.inputKey != null)
71 return false;
72 } else if (!inputKey.equals(other.inputKey))
73 return false;
74 if (variablesTuple == null) {
75 if (other.variablesTuple != null)
76 return false;
77 } else if (!variablesTuple.equals(other.variablesTuple))
78 return false;
79 return true;
80 }
81
82 public Set<TypeJudgement> getDirectlyImpliedJudgements(IQueryMetaContext context) {
83 Set<TypeJudgement> results = new HashSet<TypeJudgement>();
84 results.add(this);
85
86 Collection<InputKeyImplication> implications = context.getImplications(this.inputKey);
87 for (InputKeyImplication inputKeyImplication : implications) {
88 results.add(
89 transcribeImplication(inputKeyImplication)
90 );
91 }
92
93 return results;
94 }
95
96 /**
97 * @since 1.6
98 */
99 public Set<TypeJudgement> getWeakenedAlternativeJudgements(IQueryMetaContext context) {
100 Set<TypeJudgement> results = new HashSet<TypeJudgement>();
101
102 Collection<InputKeyImplication> implications = context.getWeakenedAlternatives(this.inputKey);
103 for (InputKeyImplication inputKeyImplication : implications) {
104 results.add(
105 transcribeImplication(inputKeyImplication)
106 );
107 }
108
109 return results;
110 }
111
112 /**
113 * @since 2.0
114 */
115 public Map<TypeJudgement, Set<TypeJudgement>> getConditionalImpliedJudgements(IQueryMetaContext context) {
116 return context.getConditionalImplications(this.inputKey).entrySet().stream().collect(Collectors.toMap(
117 entry -> transcribeImplication(entry.getKey()),
118 entry -> entry.getValue().stream().map(this::transcribeImplication).collect(Collectors.toSet())));
119 }
120
121
122
123 private TypeJudgement transcribeImplication(InputKeyImplication inputKeyImplication) {
124 return new TypeJudgement(
125 inputKeyImplication.getImpliedKey(),
126 transcribeVariablesToTuple(inputKeyImplication.getImpliedIndices())
127 );
128 }
129 private Tuple transcribeVariablesToTuple(List<Integer> indices) {
130 Object[] elements = new Object[indices.size()];
131 for (int i = 0; i < indices.size(); ++i)
132 elements[i] = variablesTuple.get(indices.get(i));
133 return Tuples.flatTupleOf(elements);
134 }
135
136 @Override
137 public String toString() {
138 return "TypeJudgement:" + inputKey.getPrettyPrintableName() + "@" + variablesTuple.toString();
139 }
140
141 /**
142 * Creates this judgement as a direct type constraint in the given PBody under construction.
143 * <p> pre: the variables tuple must be formed of variables of that PBody.
144 * @since 1.6
145 */
146 public PConstraint createConstraintFor(PBody pBody) {
147 if (inputKey.isEnumerable()) {
148 return new TypeConstraint(pBody, variablesTuple, inputKey);
149 } else {
150 return new TypeFilterConstraint(pBody, variablesTuple, inputKey);
151 }
152 }
153}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java
new file mode 100644
index 00000000..8ea6bb93
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem;
11
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
15import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
16
17/**
18 * A kind of deferred constraint that can only be checked when a set of deferring variables are all present in a plan.
19 *
20 * @author Gabor Bergmann
21 *
22 */
23public abstract class VariableDeferredPConstraint extends DeferredPConstraint {
24
25 public VariableDeferredPConstraint(PBody pBody,
26 Set<PVariable> affectedVariables) {
27 super(pBody, affectedVariables);
28 }
29
30 public abstract Set<PVariable> getDeferringVariables();
31
32 /**
33 * Refine further if needed
34 */
35 @Override
36 public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) {
37 return plan.getVisibleVariables().containsAll(getDeferringVariables());
38 }
39
40}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java
new file mode 100644
index 00000000..63a37bbe
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.aggregations;
10
11/**
12 *
13 * An aggregation operator that does not store interim results beyond the final aggregated value.
14 * @author Gabor Bergmann
15 * @since 1.4
16 */
17public abstract class AbstractMemorylessAggregationOperator<Domain, AggregateResult>
18 implements IMultisetAggregationOperator<Domain, AggregateResult, AggregateResult>
19{
20
21 @Override
22 public AggregateResult getAggregate(AggregateResult result) {
23 return result;
24 }
25
26 @Override
27 public AggregateResult clone(AggregateResult original) {
28 return original;
29 }
30
31}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java
new file mode 100644
index 00000000..4cc40a2b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.aggregations;
10
11import java.lang.annotation.ElementType;
12import java.lang.annotation.Inherited;
13import java.lang.annotation.Retention;
14import java.lang.annotation.RetentionPolicy;
15import java.lang.annotation.Target;
16
17import tools.refinery.viatra.runtime.matchers.aggregators.count;
18
19/**
20 * The aggregator type annotation describes the type constraints for the selected aggregator. In version 1.4, two kinds of
21 * aggregators are supported:
22 *
23 * <ol>
24 * <li>An aggregator that does not consider any parameter value from the call ({@link count}), just calculates the
25 * number of matches. This is represented by a single {@link Void} and a single corresponding return type.</li>
26 * <li>An aggregator that considers a single parameter from the call, and executes some aggregate operations over it.
27 * Such an aggregate operation can be defined over multiple types, where each possible parameter type has a corresponding return type declared.</li>
28 * </ol>
29 *
30 * <strong>Important!</strong> The parameterTypes and returnTypes arrays must have
31 * <ul>
32 * <li>The same number of classes defined each.</li>
33 * <li>Items are corresponded by index.</li>
34 * <li>Items should represent data types</li>
35 * </ul>
36 *
37 * @author Zoltan Ujhelyi
38 * @since 1.4
39 *
40 */
41@Target({ ElementType.TYPE })
42@Retention(RetentionPolicy.RUNTIME)
43@Inherited
44public @interface AggregatorType {
45
46 Class<?>[] parameterTypes();
47
48 Class<?>[] returnTypes();
49}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java
new file mode 100644
index 00000000..e6972544
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.aggregations;
10
11import tools.refinery.viatra.runtime.matchers.context.IInputKey;
12import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey;
13
14/**
15 * Augments an aggregator operator with type bindings for the type of values being aggregated and the aggregate result.
16 * <p> In case of <em>count</em>, the operator should be null.
17 * @author Gabor Bergmann
18 * @since 1.4
19 */
20public class BoundAggregator {
21 private final IMultisetAggregationOperator<?, ?, ?> operator;
22 private final Class<?> domainType;
23 private final Class<?> aggregateResultType;
24
25 public BoundAggregator(IMultisetAggregationOperator<?, ?, ?> operator,
26 Class<?> domainType,
27 Class<?> aggregateResultType) {
28 super();
29 this.operator = operator;
30 this.domainType = domainType;
31 this.aggregateResultType = aggregateResultType;
32 }
33
34 public IMultisetAggregationOperator<?, ?, ?> getOperator() {
35 return operator;
36 }
37
38 public Class<?> getDomainType() {
39 return domainType;
40 }
41
42 public Class<?> getAggregateResultType() {
43 return aggregateResultType;
44 }
45
46 public IInputKey getDomainTypeAsInputKey() {
47 return toJavaInputKey(domainType);
48 }
49
50 public IInputKey getAggregateResultTypeAsInputKey() {
51 return toJavaInputKey(aggregateResultType);
52 }
53
54 private static IInputKey toJavaInputKey(Class<?> type) {
55 if (type==null) {
56 return null;
57 } else {
58 return new JavaTransitiveInstancesKey(type);
59 }
60 }
61}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java
new file mode 100644
index 00000000..c970bd6a
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Zoltan Ujhelyi, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.aggregations;
10
11/**
12 *
13 * Describes an aggregation operator keyword, potentially with type polymorphism. The actual runtime
14 * {@link IMultisetAggregationOperator} that implements the aggregation logic may depend on the type context.
15 *
16 * <p>
17 * Implementors are suggested to use lower-case classnames (as it will end up in the language) and are required use the
18 * annotation {@link AggregatorType} to indicate type inference rules.
19 *
20 * <p>
21 * <strong>Important!</strong> Implemented aggregators must be (1) deterministic (2) pure and (3)support incremental
22 * value updates in the internal operation.
23 *
24 * @author Zoltan Ujhelyi
25 * @since 1.4
26 */
27
28public interface IAggregatorFactory {
29
30 /**
31 * Given type parameters selected from {@link AggregatorType} annotations, returns a run-time aggregator operator
32 * that is bound to the actual types.
33 *
34 * @param domainClass
35 * Java type of the values that are being aggregated
36 * @return the actual run-time aggregator logic, with type bindings
37 */
38 public BoundAggregator getAggregatorLogic(Class<?> domainClass);
39
40}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java
new file mode 100644
index 00000000..3bc22274
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.aggregations;
10
11import java.util.Collection;
12import java.util.stream.Stream;
13
14import tools.refinery.viatra.runtime.matchers.aggregators.ExtremumOperator;
15
16/**
17 * A single column aggregator is used to incrementally compute the aggregate of a multiset of values according to an aggregator operator.
18 *
19 * <p> The operator provides two methods of computation: <ul>
20 * <li>Stateless aggregation of an explicit multiset, provided by {@link #aggregateStatelessly(Collection)}.</li>
21 * <li>Incremental aggregation, provided by {@link #createNeutral()}, {@link #update(Object, Object, boolean)}, {@link #isNeutral(Object)}, {@link #getAggregate(Object)}.
22 * </ul>
23 *
24 * <p> In case of incremental computation, the aggregable multiset is conceptual; it is not represented by an explicit Collection<Domain> object, but its update operations are tracked.
25 *
26 * <p> In case of incremental computation, internal results, potentially distinct from the final aggregate result, may be stored in a helper data structure called <b>accumulator</b>.
27 * The goal of this distinction is that the final result may not be sufficient for incremental updates (see e.g. {@link ExtremumOperator}).
28 *
29 * @author Gabor Bergmann
30 *
31 * @param <Domain> the type of elements to be aggregated.
32 * @param <Accumulator> the type used to store the interim results of the aggregate computation,
33 * that may be incrementally refreshed upon updates to the multiset, and that can easily yield the final result.
34 * @param <AggregateResult> the type of the final result of the aggregation to be output.
35 *
36 * @since 1.4
37 */
38public interface IMultisetAggregationOperator<Domain, Accumulator, AggregateResult> {
39
40 /**
41 * A textual description of the operator.
42 */
43 String getShortDescription();
44
45 /**
46 * A name or identifier of the operator.
47 */
48 String getName();
49
50 /**
51 * @return the neutral element, i.e. the interim result of aggregating an empty multiset.
52 */
53 Accumulator createNeutral();
54
55 /**
56 * @return true if the interim result is equivalent to the neutral element, as if there are no values in the multiset.
57 * Must return true if the multiset is empty.
58 */
59 boolean isNeutral(Accumulator result);
60
61 /**
62 * @return an updated intermediate result,
63 * changed to reflect that a given object was added to / removed from the multiset
64 * (as indicated by the parameter isInsertion)
65 */
66 Accumulator update(Accumulator oldResult, Domain updateValue, boolean isInsertion);
67
68 /**
69 * @return the aggregate result obtained from the given intermediate result.
70 * May be null to indicate that the current multiset cannot be aggregated (e.g. 0 elements have no minimum).
71 */
72 AggregateResult getAggregate(Accumulator result);
73
74 /**
75 * Calculates the aggregate results from a given stream of values; all values are considered as inserted
76 * @return the aggregate result, or null if no result can be calculated (e.g. because of an empty stream)
77 * @since 2.0
78 */
79 AggregateResult aggregateStream(Stream<Domain> stream);
80
81 /**
82 * Clones the given accumulator (with all its internal contents).
83 */
84 default Accumulator clone(Accumulator original) {
85 throw new UnsupportedOperationException();
86 }
87
88 /**
89 * Combines the given aggregate result and accumulator into a single aggregate result.
90 */
91 default AggregateResult combine(AggregateResult left, Accumulator right) {
92 throw new UnsupportedOperationException();
93 }
94
95 default boolean contains(Domain value, Accumulator accumulator) {
96 throw new UnsupportedOperationException();
97 }
98
99 /**
100 * Pretty prints the contents of the given accumulator.
101 */
102 default String prettyPrint(final Accumulator accumulator) {
103 return accumulator.toString();
104 }
105
106}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java
new file mode 100644
index 00000000..e3f28cff
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java
@@ -0,0 +1,194 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.analysis;
10
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16import java.util.stream.Collectors;
17
18import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
19import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
20import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper;
21import tools.refinery.viatra.runtime.matchers.psystem.PBody;
22import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
23import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
24import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
25import tools.refinery.viatra.runtime.matchers.psystem.annotations.ParameterReference;
26import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
27import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
28import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
29import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
30
31/**
32 * Object responsible for computing and caching static query analysis results.
33 * <p> Any client can instantiate this to statically analyze queries.
34 * Query backends should share an instance obtained via {@link IQueryBackendContext} to save resources.
35 * <p> Precondition: all involved queries must be initialized.
36 * @noinstantiate Considered unstable API; subject to change in future versions.
37 * Either use the analyzer provided by {@link IQueryBackendContext}, or anticipate
38 * potential future breakage when instantiating your own analyzer.
39 * @author Gabor Bergmann
40 * @since 1.5
41 */
42public final class QueryAnalyzer {
43
44 private IQueryMetaContext metaContext;
45
46 public QueryAnalyzer(IQueryMetaContext metaContext) {
47 this.metaContext = metaContext;
48 }
49
50 // Functional dependencies
51
52 /**
53 * Maps query and strictness to functional dependencies
54 */
55 private Map<PQuery, Map<Set<Integer>, Set<Integer>>> strictFunctionalDependencyGuarantees =
56 new HashMap<>();
57 private Map<PQuery, Map<Set<Integer>, Set<Integer>>> softFunctionalDependencyGuarantees =
58 new HashMap<>();
59
60 /**
61 * Functional dependency information, expressed on query parameters, that the match set of the query is guaranteed to respect.
62 * <p> The type dependencies shall be expressed on the <i>parameter index</i> integers, NOT the {@link PParameter} object.
63 * @return a non-null map of functional dependencies on parameters that can be processed by {@link FunctionalDependencyHelper}
64 * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation;
65 * if false, user-provided soft dependencies (@FunctionalDependency) are included as well, that are anticipated but not guaranteed by the storage mechanism;
66 * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance
67 * @since 1.5
68 */
69 public Map<Set<Integer>, Set<Integer>> getProjectedFunctionalDependencies(PQuery query, boolean strict) {
70 Map<PQuery, Map<Set<Integer>, Set<Integer>>> guaranteeStore = strict ? strictFunctionalDependencyGuarantees : softFunctionalDependencyGuarantees;
71 Map<Set<Integer>, Set<Integer>> dependencies = guaranteeStore.get(query);
72 // Why not computeIfAbsent? See Bug 532507
73 // Invoked method #computeFunctionalDependencies may trigger functional dependency computation for called queries;
74 // and may thus recurs back into #getProjectedFunctionalDependencies, causing a ConcurrentModificationException
75 // if the called query has not been previously analyzed.
76 //
77 // Note: if patterns are recursive, the empty accumulator will be found in the store
78 // (this yields a safe lower estimate and guarantees termination for #getProjectedFunctionalDependencies)
79 // But this case probably will not occur due to recursive queries having a disjunction at some point,
80 // and thus ignored by #computeFunctionalDependencies
81 if (dependencies == null) {
82 dependencies = new HashMap<>(); // accumulator
83 guaranteeStore.put(query, dependencies);
84 computeFunctionalDependencies(dependencies, query, strict);
85 }
86 return dependencies;
87 }
88
89 private void computeFunctionalDependencies(Map<Set<Integer>, Set<Integer>> accumulator, PQuery query, boolean strict) {
90 Set<PBody> bodies = query.getDisjunctBodies().getBodies();
91 if (bodies.size() == 1) { // no support for recursion or disjunction
92
93 PBody body = bodies.iterator().next();
94
95 // collect parameter variables
96 Map<PVariable, Integer> parameters = body.getSymbolicParameters().stream()
97 .collect(Collectors.toMap(ExportedParameter::getParameterVariable,
98 param -> query.getParameters().indexOf(param.getPatternParameter())));
99
100 // collect all internal dependencies
101 Map<Set<PVariable>, Set<PVariable>> internalDependencies =
102 getFunctionalDependencies(body.getConstraints(), strict);
103
104 // project onto parameter variables
105 Map<Set<PVariable>, Set<PVariable>> projectedDeps =
106 FunctionalDependencyHelper.projectDependencies(internalDependencies, parameters.keySet());
107
108 // translate into indices
109 for (Entry<Set<PVariable>, Set<PVariable>> entry : projectedDeps.entrySet()) {
110 Set<Integer> left = new HashSet<Integer>();
111 Set<Integer> right = new HashSet<Integer>();
112 for (PVariable pVariable : entry.getKey()) {
113 left.add(parameters.get(pVariable));
114 }
115 for (PVariable pVariable : entry.getValue()) {
116 right.add(parameters.get(pVariable));
117 }
118 accumulator.put(left, right);
119 }
120
121 } else {
122 // Disjunctive case, no dependencies are inferred
123 // TODO: we can still salvage the intersection of dependencies IF
124 // - all bodies have disjoint match sets
125 // - and we avoid recursion
126 }
127
128 // add annotation-based soft dependencies (regardless of number of bodies)
129 if (!strict) {
130 outer:
131 for (PAnnotation annotation : query.getAnnotationsByName("FunctionalDependency")) {
132 Set<Integer> lefts = new HashSet<Integer>();
133 Set<Integer> rights = new HashSet<Integer>();
134
135 for (Object object : annotation.getAllValues("forEach")) {
136 ParameterReference parameter = (ParameterReference) object;
137 Integer position = query.getPositionOfParameter(parameter.getName());
138 if (position == null) continue outer;
139 lefts.add(position);
140 }
141 for (Object object : annotation.getAllValues("unique")) {
142 ParameterReference parameter = (ParameterReference) object;
143 Integer position = query.getPositionOfParameter(parameter.getName());
144 if (position == null) continue outer;
145 rights.add(position);
146 }
147
148 FunctionalDependencyHelper.includeDependency(accumulator, lefts, rights);
149 }
150 }
151 }
152
153 /**
154 * Functional dependency information, expressed on PVariables within a body, that the selected constraints imply.
155 * @return a non-null map of functional dependencies on PVariables that can be processed by {@link FunctionalDependencyHelper}
156 * @param constraints the set of constraints whose consequences will be analyzed
157 * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation;
158 * if false, user-provided soft dependencies (@FunctionalDependency) are included as well, that are anticipated but not guaranteed by the storage mechanism;
159 * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance
160 * @since 1.5
161 */
162 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(Set<? extends PConstraint> constraints, boolean strict) {
163 Map<Set<PVariable>, Set<PVariable>> accumulator = new HashMap<Set<PVariable>, Set<PVariable>>();
164 for (PConstraint pConstraint : constraints){
165 if (pConstraint instanceof PositivePatternCall) {
166 // use query analysis results instead
167 PositivePatternCall call = (PositivePatternCall) pConstraint;
168 PQuery query = call.getSupplierKey();
169 Map<Set<Integer>, Set<Integer>> paramDependencies = getProjectedFunctionalDependencies(query, strict);
170 for (Entry<Set<Integer>, Set<Integer>> entry : paramDependencies.entrySet()) {
171 Set<PVariable> lefts = new HashSet<PVariable>();
172 Set<PVariable> rights = new HashSet<PVariable>();
173
174 for (Integer index : entry.getKey()) {
175 lefts.add(call.getVariableInTuple(index));
176 }
177 for (Integer index : entry.getValue()) {
178 rights.add(call.getVariableInTuple(index));
179 }
180
181 FunctionalDependencyHelper.includeDependency(accumulator,
182 lefts, rights);
183 }
184 } else {
185 // delegate to PConstraint
186 FunctionalDependencyHelper.includeDependencies(accumulator,
187 pConstraint.getFunctionalDependencies(metaContext));
188 }
189 }
190 return accumulator;
191 }
192
193
194}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java
new file mode 100644
index 00000000..c4fbe0e9
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.annotations;
10
11import java.util.List;
12import java.util.Optional;
13import java.util.Set;
14import java.util.function.BiConsumer;
15
16import org.eclipse.collections.api.multimap.MutableMultimap;
17import org.eclipse.collections.impl.multimap.list.FastListMultimap;
18
19/**
20 * A container describing query annotations
21 * @author Zoltan Ujhelyi
22 *
23 */
24public class PAnnotation {
25
26 private final String name;
27 private MutableMultimap<String, Object> attributes = FastListMultimap.newMultimap();
28
29 public PAnnotation(String name) {
30 this.name = name;
31
32 }
33
34 /**
35 * Adds an attribute to the annotation
36 * @param attributeName
37 * @param value
38 */
39 public void addAttribute(String attributeName, Object value) {
40 attributes.put(attributeName, value);
41 }
42
43 /**
44 * Return the name of the annotation
45 */
46 public String getName() {
47 return name;
48 }
49
50 /**
51 * Returns the value of the first occurrence of an attribute
52 * @param attributeName
53 * @return the attribute value, or null, if attribute is not available
54 * @since 2.0
55 */
56 public Optional<Object> getFirstValue(String attributeName) {
57 return getAllValues(attributeName).stream().findFirst();
58 }
59
60 /**
61 * Returns the value of the first occurrence of an attribute
62 * @param attributeName
63 * @return the attribute value, or null, if attribute is not available
64 * @since 2.0
65 */
66 public <T> Optional<T> getFirstValue(String attributeName, Class<T> clazz) {
67 return getAllValues(attributeName).stream().filter(clazz::isInstance).map(clazz::cast).findFirst();
68 }
69
70 /**
71 * Returns all values of a selected attribute
72 * @param attributeName
73 * @return a non-null, but possibly empty list of attributes
74 */
75 public List<Object> getAllValues(String attributeName) {
76 return attributes.get(attributeName).toList();
77 }
78
79 /**
80 * Executes a consumer over all attributes. A selected attribute name (key) can appear (and thus consumed) multiple times.
81 * @since 2.0
82 */
83 public void forEachValue(BiConsumer<String, Object> consumer) {
84 attributes.forEachKeyValue(consumer::accept);
85 }
86
87 /**
88 * Returns a set of all attribute names used in this annotation
89 * @since 2.1
90 */
91 public Set<String> getAllAttributeNames() {
92 return attributes.keySet().toSet();
93 }
94}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java
new file mode 100644
index 00000000..c67e9046
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.annotations;
10
11/**
12 * An annotation parameter referencing a query parameter by name. Does not check whether the parameter exists.
13 *
14 * @author Zoltan Ujhelyi
15 *
16 */
17public class ParameterReference {
18
19 final String name;
20
21 public ParameterReference(String name) {
22 super();
23 this.name = name;
24 }
25
26 public String getName() {
27 return name;
28 }
29
30}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java
new file mode 100644
index 00000000..56f86e89
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java
@@ -0,0 +1,98 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.context.IInputKey;
18import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
19import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PBody;
21import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
22import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
23import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator;
24import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
25import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
26import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
27
28/**
29 * The PSystem representation of an aggregation.
30 *
31 * @author Tamas Szabo
32 * @since 1.4
33 */
34public class AggregatorConstraint extends PatternCallBasedDeferred implements ITypeInfoProviderConstraint {
35
36 protected PVariable resultVariable;
37 private BoundAggregator aggregator;
38 protected int aggregatedColumn;
39
40 public AggregatorConstraint(BoundAggregator aggregator, PBody pBody, Tuple actualParametersTuple, PQuery query,
41 PVariable resultVariable, int aggregatedColumn) {
42 super(pBody, actualParametersTuple, query, Collections.singleton(resultVariable));
43 this.resultVariable = resultVariable;
44 this.aggregatedColumn = aggregatedColumn;
45 this.aggregator = aggregator;
46 }
47
48 public int getAggregatedColumn() {
49 return this.aggregatedColumn;
50 }
51
52 public BoundAggregator getAggregator() {
53 return this.aggregator;
54 }
55
56 @Override
57 public Set<PVariable> getDeducedVariables() {
58 return Collections.singleton(resultVariable);
59 }
60
61 @Override
62 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
63 final Map<Set<PVariable>, Set<PVariable>> result = new HashMap<Set<PVariable>, Set<PVariable>>();
64 result.put(getDeferringVariables(), getDeducedVariables());
65 return result;
66 }
67
68 @Override
69 protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) {
70 if (resultVariable.equals(obsolete))
71 resultVariable = replacement;
72 }
73
74 @Override
75 protected Set<PVariable> getCandidateQuantifiedVariables() {
76 return actualParametersTuple.<PVariable> getDistinctElements();
77 }
78
79 @Override
80 protected String toStringRest() {
81 return query.getFullyQualifiedName() + "@" + actualParametersTuple.toString() + "->"
82 + resultVariable.toString();
83 }
84
85 public PVariable getResultVariable() {
86 return resultVariable;
87 }
88
89 @Override
90 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
91 Set<TypeJudgement> result = new HashSet<TypeJudgement>();
92 IInputKey aggregateResultType = aggregator.getAggregateResultTypeAsInputKey();
93 if (aggregateResultType != null) {
94 result.add(new TypeJudgement(aggregateResultType, Tuples.staticArityFlatTupleOf(resultVariable)));
95 }
96 return result;
97 }
98}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java
new file mode 100644
index 00000000..7bc949a8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.Set;
14import java.util.stream.Collectors;
15import java.util.stream.Stream;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
18import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
22import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
23
24/**
25 * @author Gabor Bergmann
26 *
27 */
28public abstract class BaseTypeSafeConstraint extends
29 VariableDeferredPConstraint {
30
31 protected Set<PVariable> inputVariables;
32 protected PVariable outputVariable;
33
34 public PVariable getOutputVariable() {
35 return outputVariable;
36 }
37
38 /**
39 * @param pBody
40 * @param inputVariables
41 * @param outputVariable null iff no output (check-only)
42 */
43 public BaseTypeSafeConstraint(PBody pBody,
44 Set<PVariable> inputVariables, final PVariable outputVariable) {
45 super(pBody,
46 (outputVariable == null) ?
47 inputVariables :
48 Stream.concat(inputVariables.stream(), Stream.of(outputVariable)).collect(Collectors.toSet())
49 );
50 this.inputVariables = inputVariables;
51 this.outputVariable = outputVariable;
52 }
53
54 @Override
55 public Set<PVariable> getDeducedVariables() {
56 if (outputVariable == null)
57 return Collections.emptySet();
58 else
59 return Collections.singleton(outputVariable);
60 }
61
62 @Override
63 public Set<PVariable> getDeferringVariables() {
64 return inputVariables;
65 }
66
67 @Override
68 public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) {
69 if (super.isReadyAt(plan, context)) {
70 return checkTypeSafety(plan, context) == null;
71 }
72 return false;
73 }
74
75 /**
76 * Checks whether all type restrictions are already enforced on affected variables.
77 *
78 * @param plan
79 * @return a variable whose type safety is not enforced yet, or null if the plan is typesafe
80 */
81 public PVariable checkTypeSafety(SubPlan plan, IQueryMetaContext context) {
82 Set<TypeJudgement> impliedJudgements = plan.getAllImpliedTypeJudgements(context);
83
84 for (PVariable pVariable : inputVariables) {
85 Set<TypeJudgement> allTypeRestrictionsForVariable = pBody.getAllUnaryTypeRestrictions(context).get(pVariable);
86 if (allTypeRestrictionsForVariable != null && !impliedJudgements.containsAll(allTypeRestrictionsForVariable))
87 return pVariable;
88 }
89 return null;
90 }
91
92 @Override
93 protected void doReplaceVariable(PVariable obsolete, PVariable replacement) {
94 if (inputVariables.remove(obsolete))
95 inputVariables.add(replacement);
96 if (outputVariable == obsolete)
97 outputVariable = replacement;
98 }
99}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java
new file mode 100644
index 00000000..b978b62c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java
@@ -0,0 +1,96 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.Map;
16import java.util.Set;
17
18import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
19import tools.refinery.viatra.runtime.matchers.planning.SubPlan;
20import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint;
21import tools.refinery.viatra.runtime.matchers.psystem.PBody;
22import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
23
24/**
25 * @author Gabor Bergmann
26 *
27 */
28public class Equality extends DeferredPConstraint {
29
30 private PVariable who;
31 private PVariable withWhom;
32
33 public Equality(PBody pBody, PVariable who, PVariable withWhom) {
34 super(pBody, buildSet(who, withWhom));
35 this.who = who;
36 this.withWhom = withWhom;
37 }
38
39 private static Set<PVariable> buildSet(PVariable who, PVariable withWhom) {
40 Set<PVariable> set = new HashSet<PVariable>();
41 set.add(who);
42 set.add(withWhom);
43 return set;
44 }
45
46 /**
47 * An equality is moot if it compares the a variable with itself.
48 *
49 * @return true, if the equality is moot
50 */
51 public boolean isMoot() {
52 return who.equals(withWhom);
53 }
54
55 @Override
56 public void doReplaceVariable(PVariable obsolete, PVariable replacement) {
57 if (obsolete.equals(who))
58 who = replacement;
59 if (obsolete.equals(withWhom))
60 withWhom = replacement;
61 }
62
63 @Override
64 protected String toStringRest() {
65 return who.getName() + "=" + withWhom.getName();
66 }
67
68 public PVariable getWho() {
69 return who;
70 }
71
72 public PVariable getWithWhom() {
73 return withWhom;
74 }
75
76 @Override
77 public Set<PVariable> getDeducedVariables() {
78 return Collections.emptySet();
79 }
80
81 @Override
82 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
83 final Map<Set<PVariable>, Set<PVariable>> result = new HashMap<Set<PVariable>, Set<PVariable>>();
84 result.put(Collections.singleton(who), Collections.singleton(withWhom));
85 result.put(Collections.singleton(withWhom), Collections.singleton(who));
86 return result;
87 }
88
89 @Override
90 public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) {
91 return plan.getVisibleVariables().contains(who) && plan.getVisibleVariables().contains(withWhom);
92 // will be replaced by || if copierNode is available;
93 // until then, LayoutHelper.unifyVariablesAlongEqualities(PSystem<PatternDescription, StubHandle, Collector>) is
94 // recommended.
95 }
96}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java
new file mode 100644
index 00000000..80706792
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java
@@ -0,0 +1,108 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
16import tools.refinery.viatra.runtime.matchers.psystem.PBody;
17import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
18import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
20import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
21
22/**
23 * @author Gabor Bergmann
24 *
25 */
26public class ExportedParameter extends VariableDeferredPConstraint {
27 PVariable parameterVariable;
28 final String parameterName;
29 final PParameter patternParameter;
30
31 /**
32 * @since 1.4
33 */
34 public ExportedParameter(PBody pBody, PVariable parameterVariable, PParameter patternParameter) {
35 super(pBody, Collections.singleton(parameterVariable));
36 this.parameterVariable = parameterVariable;
37 this.patternParameter = patternParameter;
38 parameterName = patternParameter.getName();
39 }
40
41 @Override
42 public void doReplaceVariable(PVariable obsolete, PVariable replacement) {
43 if (obsolete.equals(parameterVariable))
44 parameterVariable = replacement;
45 }
46
47 @Override
48 protected String toStringRest() {
49 Object varName = parameterVariable.getName();
50 return parameterName.equals(varName) ? parameterName : parameterName + "(" + varName + ")";
51 }
52
53 @Override
54 public Set<PVariable> getDeducedVariables() {
55 return Collections.emptySet();
56 }
57
58 /**
59 * The name of the parameter; usually, it is expected that {@link #getParameterVariable()} is more useful, except
60 * maybe for debugging purposes.
61 *
62 * @return a non-null name of the parameter
63 */
64 public String getParameterName() {
65 return parameterName;
66 }
67
68 public PVariable getParameterVariable() {
69 return parameterVariable;
70 }
71
72 /**
73 * @since 1.4
74 */
75 public PParameter getPatternParameter() {
76 if (patternParameter == null) {
77 PQuery query = pBody.getPattern();
78 Integer index = query.getPositionOfParameter(parameterName);
79 if (index == null) {
80 throw new IllegalStateException(String.format("Pattern %s does not have a parameter named %s",
81 query.getFullyQualifiedName(), parameterName));
82 }
83 return query.getParameters().get(index);
84 } else {
85 return patternParameter;
86 }
87 }
88
89 @Override
90 public Set<PVariable> getDeferringVariables() {
91 return Collections.singleton(parameterVariable);
92 }
93
94 @Override
95 public void checkSanity() {
96 super.checkSanity();
97 if (!parameterVariable.isDeducable()) {
98 String[] args = { parameterName };
99 String msg = "Impossible to match pattern: "
100 + "exported pattern variable {1} can not be determined based on the pattern constraints. "
101 + "HINT: certain constructs (e.g. negative patterns or check expressions) cannot output symbolic parameters.";
102 String shortMsg = "Could not deduce value of parameter";
103 throw new QueryProcessingException(msg, args, shortMsg, null);
104 }
105
106 }
107
108}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java
new file mode 100644
index 00000000..06688c36
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.LinkedHashSet;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
18import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
22
23/**
24 * @author Zoltan Ujhelyi
25 *
26 */
27public class ExpressionEvaluation extends BaseTypeSafeConstraint {
28
29 private IExpressionEvaluator evaluator;
30 private boolean isUnwinding;
31
32 public ExpressionEvaluation(PBody pBody, IExpressionEvaluator evaluator, PVariable outputVariable) {
33 this(pBody, evaluator, outputVariable, false);
34 }
35
36 /**
37 * @since 2.4
38 */
39 public ExpressionEvaluation(PBody pBody, IExpressionEvaluator evaluator, PVariable outputVariable,
40 boolean isUnwinding) {
41 super(pBody, getPVariablesOfExpression(pBody, evaluator), outputVariable);
42 this.evaluator = evaluator;
43 this.isUnwinding = isUnwinding;
44 }
45
46 /**
47 * @since 2.4
48 */
49 public boolean isUnwinding() {
50 return isUnwinding;
51 }
52
53 public IExpressionEvaluator getEvaluator() {
54 return evaluator;
55 }
56
57 @Override
58 protected String toStringRest() {
59 return Tuples.flatTupleOf(new ArrayList<PVariable>(inputVariables).toArray()).toString() + "|="
60 + evaluator.getShortDescription();
61 }
62
63 @Override
64 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
65 if (outputVariable == null)
66 return Collections.emptyMap();
67 else
68 return Collections.singletonMap(inputVariables, Collections.singleton(outputVariable));
69 }
70
71 private static Set<PVariable> getPVariablesOfExpression(PBody pBody, IExpressionEvaluator evaluator) {
72 // use a linked set, so that the variables will come in the order of the parameters
73 Set<PVariable> result = new LinkedHashSet<PVariable>();
74 for (String name : evaluator.getInputParameterNames()) {
75 PVariable variable = pBody.getOrCreateVariableByName(name);
76 result.add(variable);
77 }
78 return result;
79 }
80}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java
new file mode 100644
index 00000000..5cac33dc
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java
@@ -0,0 +1,151 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.HashSet;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.psystem.PBody;
18import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
19import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
20
21/**
22 * @author Gabor Bergmann
23 *
24 */
25public class Inequality extends VariableDeferredPConstraint {
26
27 private PVariable who;
28 private PVariable withWhom;
29
30 /**
31 * The inequality constraint is weak if it can be ignored when who is the same as withWhom, or if any if them is
32 * undeducible.
33 */
34 private boolean weak;
35
36 public Inequality(PBody pBody, PVariable who, PVariable withWhom) {
37 this(pBody, who, withWhom, false);
38 }
39
40 public Inequality(PBody pBody, PVariable who, PVariable withWhom,
41 boolean weak) {
42 super(pBody, new HashSet<>(Arrays.asList(who, withWhom) ));
43 this.who = who;
44 this.withWhom = withWhom;
45 this.weak = weak;
46 }
47
48 // private Inequality(
49 // PSystem<PatternDescription, StubHandle, ?> pSystem,
50 // PVariable subject, Set<PVariable> inequals)
51 // {
52 // super(pSystem, include(inequals, subject));
53 // this.subject = subject;
54 // this.inequals = inequals;
55 // }
56
57 // private static HashSet<PVariable> include(Set<PVariable> inequals, PVariable subject) {
58 // HashSet<PVariable> hashSet = new HashSet<PVariable>(inequals);
59 // hashSet.add(subject);
60 // return hashSet;
61 // }
62
63 @Override
64 public Set<PVariable> getDeferringVariables() {
65 return getAffectedVariables();
66 }
67
68 // private static int[] mapIndices(Map<Object, Integer> variablesIndex, Set<PVariable> keys) {
69 // int[] result = new int[keys.size()];
70 // int k = 0;
71 // for (PVariable key : keys) {
72 // result[k++] = variablesIndex.get(key);
73 // }
74 // return result;
75 // }
76
77 // @Override
78 // public IFoldablePConstraint getIncorporator() {
79 // return incorporator;
80 // }
81 //
82 // @Override
83 // public void registerIncorporatationInto(IFoldablePConstraint incorporator) {
84 // this.incorporator = incorporator;
85 // }
86 //
87 // @Override
88 // public boolean incorporate(IFoldablePConstraint other) {
89 // if (other instanceof Inequality<?, ?>) {
90 // Inequality other2 = (Inequality) other;
91 // if (subject.equals(other2.subject)) {
92 // Set<PVariable> newInequals = new HashSet<PVariable>(inequals);
93 // newInequals.addAll(other2.inequals);
94 // return new Inequality<PatternDescription, StubHandle>(buildable, subject, newInequals);
95 // }
96 // } else return false;
97 // }
98
99 @Override
100 protected String toStringRest() {
101 return who.toString() + (isWeak() ? "!=?" : "!=") + withWhom.toString();
102 }
103
104 @Override
105 public void doReplaceVariable(PVariable obsolete, PVariable replacement) {
106 if (obsolete.equals(who))
107 who = replacement;
108 if (obsolete.equals(withWhom))
109 withWhom = replacement;
110 }
111
112 @Override
113 public Set<PVariable> getDeducedVariables() {
114 return Collections.emptySet();
115 }
116
117 /**
118 * The inequality constraint is weak if it can be ignored when who is the same as withWhom, or if any if them is
119 * undeducible.
120 *
121 * @return the weak
122 */
123 public boolean isWeak() {
124 return weak;
125 }
126
127 /**
128 * A weak inequality constraint is eliminable if who is the same as withWhom, or if any if them is undeducible.
129 */
130 public boolean isEliminable() {
131 return isWeak() && (who.equals(withWhom) || !who.isDeducable() || !withWhom.isDeducable());
132 }
133
134 /**
135 * Eliminates a weak inequality constraint if it can be ignored when who is the same as withWhom, or if any if them
136 * is undeducible.
137 */
138 public void eliminateWeak() {
139 if (isEliminable())
140 delete();
141 }
142
143 public PVariable getWho() {
144 return who;
145 }
146
147 public PVariable getWithWhom() {
148 return withWhom;
149 }
150
151}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java
new file mode 100644
index 00000000..87d9d9fc
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java
@@ -0,0 +1,52 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.psystem.PBody;
16import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19
20/**
21 * @author Gabor Bergmann
22 *
23 */
24public class NegativePatternCall extends PatternCallBasedDeferred {
25
26 public NegativePatternCall(PBody pBody, Tuple actualParametersTuple, PQuery query) {
27 super(pBody, actualParametersTuple, query);
28 }
29
30 @Override
31 public Set<PVariable> getDeducedVariables() {
32 return Collections.emptySet();
33 }
34
35 /**
36 * @return all variables that may potentially be quantified they are not used anywhere else
37 */
38 @Override
39 protected Set<PVariable> getCandidateQuantifiedVariables() {
40 return getAffectedVariables();
41 }
42
43 @Override
44 protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) {
45 }
46
47 @Override
48 protected String toStringRest() {
49 return "!" + query.getFullyQualifiedName() + "@" + actualParametersTuple.toString();
50 }
51
52}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java
new file mode 100644
index 00000000..93eeffec
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java
@@ -0,0 +1,118 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.Set;
15
16import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
17import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
18import tools.refinery.viatra.runtime.matchers.psystem.PBody;
19import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24
25/**
26 * @author Gabor Bergmann
27 *
28 */
29public abstract class PatternCallBasedDeferred extends VariableDeferredPConstraint implements IQueryReference {
30
31 protected Tuple actualParametersTuple;
32
33 protected abstract void doDoReplaceVariables(PVariable obsolete, PVariable replacement);
34
35 protected abstract Set<PVariable> getCandidateQuantifiedVariables();
36
37 protected PQuery query;
38 private Set<PVariable> deferringVariables;
39
40 public PatternCallBasedDeferred(PBody pBody, Tuple actualParametersTuple,
41 PQuery pattern, Set<PVariable> additionalAffectedVariables) {
42 super(pBody, union(actualParametersTuple.<PVariable> getDistinctElements(), additionalAffectedVariables));
43 this.actualParametersTuple = actualParametersTuple;
44 this.query = pattern;
45 }
46
47 public PatternCallBasedDeferred(PBody pBody, Tuple actualParametersTuple,
48 PQuery pattern) {
49 this(pBody, actualParametersTuple, pattern, Collections.<PVariable> emptySet());
50 }
51
52 private static Set<PVariable> union(Set<PVariable> a, Set<PVariable> b) {
53 Set<PVariable> result = new HashSet<PVariable>();
54 result.addAll(a);
55 result.addAll(b);
56 return result;
57 }
58
59 @Override
60 public Set<PVariable> getDeferringVariables() {
61 if (deferringVariables == null) {
62 deferringVariables = new HashSet<PVariable>();
63 for (PVariable var : getCandidateQuantifiedVariables()) {
64 if (var.isDeducable())
65 deferringVariables.add(var);
66 }
67 }
68 return deferringVariables;
69 }
70
71 @Override
72 public void checkSanity() {
73 super.checkSanity();
74 for (Object obj : this.actualParametersTuple.getDistinctElements()) {
75 PVariable var = (PVariable) obj;
76 if (!getDeferringVariables().contains(var)) {
77 // so this is a free variable of the NAC / aggregation?
78 for (PConstraint pConstraint : var.getReferringConstraints()) {
79 if (pConstraint != this
80 && !(pConstraint instanceof Equality && ((Equality) pConstraint).isMoot()))
81 throw new QueryProcessingException (
82 "Variable {1} of constraint {2} is not a positively determined part of the pattern, yet it is also affected by {3}.",
83 new String[] { var.toString(), this.toString(), pConstraint.toString() },
84 "Read-only variable can not be deduced", null);
85 }
86 }
87 }
88
89 }
90
91// public SubPlan getSidePlan(IOperationCompiler compiler) throws QueryPlannerException {
92// SubPlan sidePlan = compiler.patternCallPlan(actualParametersTuple, query);
93// sidePlan = BuildHelper.enforceVariableCoincidences(compiler, sidePlan);
94// return sidePlan;
95// }
96
97 @Override
98 protected void doReplaceVariable(PVariable obsolete, PVariable replacement) {
99 if (deferringVariables != null) {
100 // FAIL instead of hopeless attempt to fix
101 // if (deferringVariables.remove(obsolete)) deferringVariables.add(replacement);
102 throw new IllegalStateException("Cannot replace variables on " + this
103 + " when deferring variables have already been identified.");
104 }
105 actualParametersTuple = actualParametersTuple.replaceAll(obsolete, replacement);
106 doDoReplaceVariables(obsolete, replacement);
107 }
108
109 public Tuple getActualParametersTuple() {
110 return actualParametersTuple;
111 }
112
113 @Override
114 public PQuery getReferredQuery() {
115 return query;
116 }
117
118}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java
new file mode 100644
index 00000000..0c40d91e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
18import tools.refinery.viatra.runtime.matchers.psystem.PBody;
19import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
20import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
22
23/**
24 * @author Gabor Bergmann
25 */
26public class PatternMatchCounter extends PatternCallBasedDeferred {
27
28 private PVariable resultVariable;
29
30 public PatternMatchCounter(PBody pBody, Tuple actualParametersTuple,
31 PQuery query, PVariable resultVariable) {
32 super(pBody, actualParametersTuple, query, Collections.singleton(resultVariable));
33 this.resultVariable = resultVariable;
34 }
35
36 @Override
37 public Set<PVariable> getDeducedVariables() {
38 return Collections.singleton(resultVariable);
39 }
40
41 @Override
42 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
43 final Map<Set<PVariable>, Set<PVariable>> result = new HashMap<Set<PVariable>, Set<PVariable>>();
44 result.put(getDeferringVariables(), getDeducedVariables());
45 return result;
46 }
47
48 @Override
49 protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) {
50 if (resultVariable.equals(obsolete))
51 resultVariable = replacement;
52 }
53
54 @Override
55 protected Set<PVariable> getCandidateQuantifiedVariables() {
56 return actualParametersTuple.<PVariable> getDistinctElements();
57 }
58
59
60 @Override
61 protected String toStringRest() {
62 return query.getFullyQualifiedName() + "@" + actualParametersTuple.toString() + "->"
63 + resultVariable.toString();
64 }
65
66 public PVariable getResultVariable() {
67 return resultVariable;
68 }
69
70} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java
new file mode 100644
index 00000000..336a83fb
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2022, Tamas Szabo, GitHub
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
10
11import java.util.List;
12
13import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
14import tools.refinery.viatra.runtime.matchers.psystem.IMultiQueryReference;
15import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator;
16import tools.refinery.viatra.runtime.matchers.psystem.PBody;
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19
20/**
21 * A constraint which prescribes the evaluation of custom Java logic that takes an arbitrary number of input relations
22 * and produces one output relation. Contrast this to {@link ExpressionEvaluation}, which produces a single output value
23 * given an input tuple.
24 *
25 * The assumption is that the relation evaluation logic is not incremental, that is, it can only perform from-scratch
26 * computation of the output relation given the complete input relations. To this end, the relation evaluator always
27 * receives the complete input relations with all their contents as input. However, the evaluator engine makes sure that
28 * the output of the relation evaluation is at least "seemingly" incremental. This means that the underlying computation
29 * network computes the delta on the output compared to the previous output and only propagates the delta further.
30 *
31 * @author Tamas Szabo
32 *
33 * @since 2.8
34 *
35 */
36public class RelationEvaluation extends EnumerablePConstraint implements IMultiQueryReference {
37
38 private final IRelationEvaluator evaluator;
39 private final List<PQuery> inputQueries;
40
41 public RelationEvaluation(final PBody body, final Tuple variablesTuple, final List<PQuery> inputQueries,
42 final IRelationEvaluator evaluator) {
43 super(body, variablesTuple);
44 this.evaluator = evaluator;
45 this.inputQueries = inputQueries;
46 }
47
48 public IRelationEvaluator getEvaluator() {
49 return this.evaluator;
50 }
51
52 @Override
53 public List<PQuery> getReferredQueries() {
54 return this.inputQueries;
55 }
56
57}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java
new file mode 100644
index 00000000..8b6e29ef
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.context.IInputKey;
16import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
17import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint;
18import tools.refinery.viatra.runtime.matchers.psystem.PBody;
19import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
20import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
21import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint;
22import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24
25/**
26 * Represents a non-enumerable type constraint that asserts that values substituted for the given tuple of variables
27 * form a tuple that belongs to a (typically non-enumerable) extensional relation identified by an {@link IInputKey}.
28 *
29 * <p> The InputKey is typically not enumerable. If it is enumerable, use {@link TypeConstraint} instead, so that the PConstraint carries over the property of enumerability.
30 *
31 * @author Bergmann Gabor
32 *
33 */
34public class TypeFilterConstraint extends VariableDeferredPConstraint implements
35 ITypeConstraint {
36
37 private Tuple variablesTuple;
38 private IInputKey inputKey;
39
40 private TypeJudgement equivalentJudgement;
41
42
43 public TypeFilterConstraint(PBody pBody, Tuple variablesTuple, IInputKey inputKey) {
44 super(pBody, variablesTuple.<PVariable> getDistinctElements());
45 this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple);
46
47 this.variablesTuple = variablesTuple;
48 this.inputKey = inputKey;
49
50 if (variablesTuple.getSize() != inputKey.getArity())
51 throw new IllegalArgumentException(
52 this.getClass().getSimpleName() +
53 " applied for variable tuple " + variablesTuple + " having wrong arity for input key " +
54 inputKey);
55 }
56
57
58
59 public Tuple getVariablesTuple() {
60 return variablesTuple;
61 }
62
63 public IInputKey getInputKey() {
64 return inputKey;
65 }
66
67 @Override
68 public TypeJudgement getEquivalentJudgement() {
69 return equivalentJudgement;
70 }
71
72 @Override
73 protected void doReplaceVariable(PVariable obsolete, PVariable replacement) {
74 variablesTuple = variablesTuple.replaceAll(obsolete, replacement);
75 this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple);
76 }
77
78 @Override
79 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
80 return Collections.singleton(equivalentJudgement);
81 }
82
83 @Override
84 public Set<PVariable> getDeducedVariables() {
85 return Collections.emptySet();
86 }
87
88 @Override
89 public Set<PVariable> getDeferringVariables() {
90 return getAffectedVariables();
91 }
92
93 @Override
94 protected String toStringRest() {
95 return inputKey.getPrettyPrintableName() + "@" + variablesTuple;
96 }
97
98 @Override
99 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
100 return TypeConstraintUtil.getFunctionalDependencies(context, inputKey, variablesTuple);
101 }
102
103
104
105}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java
new file mode 100644
index 00000000..7bbf7118
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
10
11import java.util.Set;
12
13import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
14import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
15import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint;
16import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint;
17import tools.refinery.viatra.runtime.matchers.psystem.PBody;
18import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
19import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
21
22/**
23 * @since 2.0
24 */
25public abstract class AbstractTransitiveClosure extends KeyedEnumerablePConstraint<PQuery> implements IQueryReference, ITypeInfoProviderConstraint {
26
27 public AbstractTransitiveClosure(PBody pBody, Tuple variablesTuple, PQuery supplierKey) {
28 super(pBody, variablesTuple, supplierKey);
29 }
30
31 @Override
32 public PQuery getReferredQuery() {
33 return supplierKey;
34 }
35
36 /**
37 * @since 1.3
38 */
39 @Override
40 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
41 return PositivePatternCall.getTypesImpliedByCall(supplierKey, variablesTuple);
42 }
43
44} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java
new file mode 100644
index 00000000..e3dae240
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Zoltan Ujhelyi, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
11
12import tools.refinery.viatra.runtime.matchers.context.IInputKey;
13import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
14import tools.refinery.viatra.runtime.matchers.psystem.PBody;
15import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
16import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
17
18/**
19 * For a binary base pattern over an enumerable universe type, computes the reflexive transitive closure (base)*
20 *
21 * @author Gabor Bergmann, Zoltan Ujhelyi
22 * @since 2.0
23 */
24public class BinaryReflexiveTransitiveClosure extends AbstractTransitiveClosure {
25
26 private final IInputKey universeType;
27
28 public BinaryReflexiveTransitiveClosure(PBody pBody, Tuple variablesTuple,
29 PQuery pattern, IInputKey universeType) {
30 super(pBody, variablesTuple, pattern);
31 this.universeType = universeType;
32 }
33
34 @Override
35 protected String keyToString() {
36 return supplierKey.getFullyQualifiedName() + "*";
37 }
38
39 /**
40 * Returns the type whose instances should be returned as 0-long paths.
41 * @since 2.0
42 */
43 public IInputKey getUniverseType() {
44 return universeType;
45 }
46
47 @Override
48 public void checkSanity() {
49 if (!universeType.isEnumerable() || universeType.getArity() != 1) {
50 throw new QueryProcessingException(
51 String.format("Invalid universe type %s - it should be enumerable and must have an arity of 1.",
52 universeType.getPrettyPrintableName()),
53 pBody.getPattern());
54 }
55 }
56
57}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java
new file mode 100644
index 00000000..716d043b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
11
12import tools.refinery.viatra.runtime.matchers.psystem.PBody;
13import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
14import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
15
16/**
17 * For a binary base pattern, computes the irreflexive transitive closure (base)+
18 *
19 * @author Gabor Bergmann
20 *
21 */
22public class BinaryTransitiveClosure extends AbstractTransitiveClosure {
23
24 public BinaryTransitiveClosure(PBody pBody, Tuple variablesTuple,
25 PQuery pattern) {
26 super(pBody, variablesTuple, pattern);
27 }
28
29 @Override
30 protected String keyToString() {
31 return supplierKey.getFullyQualifiedName() + "+";
32 }
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java
new file mode 100644
index 00000000..10da2e21
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
7
8public enum Connectivity {
9 WEAK,
10 STRONG;
11}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java
new file mode 100644
index 00000000..251146c8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
11
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.Map;
15import java.util.Set;
16
17import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
18import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
22
23/**
24 * @author Gabor Bergmann
25 *
26 */
27public class ConstantValue extends KeyedEnumerablePConstraint<Object> {
28
29 private PVariable variable;
30
31 public ConstantValue(PBody pBody, PVariable variable, Object value) {
32 super(pBody, Tuples.staticArityFlatTupleOf(variable), value);
33 this.variable = variable;
34 }
35
36 @Override
37 protected String keyToString() {
38 return supplierKey.toString();
39 }
40
41 /**
42 * @since 1.7
43 */
44 public PVariable getVariable() {
45 return variable;
46 }
47
48 @Override
49 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
50 final Map<Set<PVariable>, Set<PVariable>> result = new HashMap<Set<PVariable>, Set<PVariable>>();
51 final Set<PVariable> emptySet = Collections.emptySet(); // a constant value is functionally determined by everything
52 result.put(emptySet, Collections.singleton(getVariableInTuple(0)));
53 return result;
54 }
55
56
57}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java
new file mode 100644
index 00000000..25ab34b4
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java
@@ -0,0 +1,76 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
11
12import java.util.HashSet;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.context.IInputKey;
16import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
17import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
18import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PBody;
21import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
25
26/**
27 * @author Gabor Bergmann
28 *
29 */
30public class PositivePatternCall extends KeyedEnumerablePConstraint<PQuery> implements IQueryReference, ITypeInfoProviderConstraint {
31
32 public PositivePatternCall(PBody pBody, Tuple variablesTuple,
33 PQuery pattern) {
34 super(pBody, variablesTuple, pattern);
35 }
36
37 @Override
38 protected String keyToString() {
39 return supplierKey.getFullyQualifiedName();
40 }
41
42 // Note: #getFunctionalDependencies is intentionally not implemented - use QueryAnalyzer instead!
43// @Override
44// public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
45// return super.getFunctionalDependencies(context);
46// }
47
48 @Override
49 public PQuery getReferredQuery() {
50 return supplierKey;
51 }
52
53 @Override
54 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
55 return getTypesImpliedByCall(supplierKey, variablesTuple);
56 }
57
58 /**
59 * @since 1.3
60 */
61 public static Set<TypeJudgement> getTypesImpliedByCall(PQuery calledQuery, Tuple actualParametersTuple) {
62 Set<TypeJudgement> result = new HashSet<TypeJudgement>();
63 for (TypeJudgement parameterJudgement : calledQuery.getTypeGuarantees()) {
64 IInputKey inputKey = parameterJudgement.getInputKey();
65 Tuple judgementIndexTuple = parameterJudgement.getVariablesTuple();
66
67 Object[] judgementVariables = new Object[judgementIndexTuple.getSize()];
68 for (int i=0; i<judgementVariables.length; ++i)
69 judgementVariables[i] = actualParametersTuple.get((int) judgementIndexTuple.get(i));
70
71 result.add(new TypeJudgement(inputKey, Tuples.flatTupleOf(judgementVariables)));
72 }
73 return result;
74 }
75
76}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java
new file mode 100644
index 00000000..b97ff55f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java
@@ -0,0 +1,43 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
7
8import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
9import tools.refinery.viatra.runtime.matchers.psystem.*;
10import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
11import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
12
13import java.util.Set;
14
15public class RepresentativeElectionConstraint extends KeyedEnumerablePConstraint<PQuery>
16 implements IQueryReference, ITypeInfoProviderConstraint {
17 private final Connectivity connectivity;
18
19 public RepresentativeElectionConstraint(PBody pBody, Tuple variablesTuple, PQuery supplierKey,
20 Connectivity connectivity) {
21 super(pBody, variablesTuple, supplierKey);
22 this.connectivity = connectivity;
23 }
24
25 public Connectivity getConnectivity() {
26 return connectivity;
27 }
28
29 @Override
30 public PQuery getReferredQuery() {
31 return supplierKey;
32 }
33
34 @Override
35 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
36 return PositivePatternCall.getTypesImpliedByCall(supplierKey, variablesTuple);
37 }
38
39 @Override
40 protected String keyToString() {
41 return supplierKey.getFullyQualifiedName() + "#" + connectivity + "#representative";
42 }
43}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java
new file mode 100644
index 00000000..2ca54cc0
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java
@@ -0,0 +1,79 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Set;
14
15import tools.refinery.viatra.runtime.matchers.context.IInputKey;
16import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
17import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint;
18import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint;
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
21import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
22import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
23
24/**
25 * Represents an enumerable type constraint that asserts that values substituted for the given tuple of variables
26 * form a tuple that belongs to an enumerable extensional relation identified by an {@link IInputKey}.
27 *
28 * <p> The InputKey must be enumerable!
29 *
30 * @author Zoltan Ujhelyi
31 *
32 */
33public class TypeConstraint extends KeyedEnumerablePConstraint<IInputKey> implements ITypeConstraint {
34
35 private TypeJudgement equivalentJudgement;
36
37 public TypeConstraint(PBody pBody, Tuple variablesTuple, IInputKey inputKey) {
38 super(pBody, variablesTuple, inputKey);
39 this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple);
40
41 if (! inputKey.isEnumerable())
42 throw new IllegalArgumentException(
43 this.getClass().getSimpleName() +
44 " applicable for enumerable input keys only; received instead " +
45 inputKey);
46 if (variablesTuple.getSize() != inputKey.getArity())
47 throw new IllegalArgumentException(
48 this.getClass().getSimpleName() +
49 " applied for variable tuple " + variablesTuple + " having wrong arity for input key " +
50 inputKey);
51 }
52
53 @Override
54 protected String keyToString() {
55 return supplierKey.getPrettyPrintableName();
56 }
57
58 @Override
59 public TypeJudgement getEquivalentJudgement() {
60 return equivalentJudgement;
61 }
62
63 @Override
64 public Set<TypeJudgement> getImpliedJudgements(IQueryMetaContext context) {
65 return Collections.singleton(equivalentJudgement);
66 //return equivalentJudgement.getDirectlyImpliedJudgements(context);
67 }
68
69 @Override
70 public Map<Set<PVariable>, Set<PVariable>> getFunctionalDependencies(IQueryMetaContext context) {
71 return TypeConstraintUtil.getFunctionalDependencies(context, supplierKey, variablesTuple);
72 }
73
74 @Override
75 public void doReplaceVariable(PVariable obsolete, PVariable replacement) {
76 super.doReplaceVariable(obsolete, replacement);
77 this.equivalentJudgement = new TypeJudgement(getSupplierKey(), variablesTuple);
78 }
79} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java
new file mode 100644
index 00000000..2c03a894
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java
@@ -0,0 +1,231 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.ArrayList;
12import java.util.Collections;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Objects;
16import java.util.Optional;
17import java.util.Set;
18import java.util.stream.Collectors;
19import java.util.stream.Stream;
20
21import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
22import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory;
23import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
24import tools.refinery.viatra.runtime.matchers.context.IInputKey;
25import tools.refinery.viatra.runtime.matchers.psystem.PBody;
26import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
27import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
28import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
29import tools.refinery.viatra.runtime.matchers.util.Preconditions;
30
31/**
32 * Default implementation of PQuery.
33 *
34 * @author Bergmann Gabor
35 */
36public abstract class BasePQuery implements PQuery {
37
38 protected PQueryStatus status = PQueryStatus.UNINITIALIZED;
39 /**
40 * @since 2.0
41 */
42 protected final PVisibility visibility;
43 protected List<PProblem> pProblems = new ArrayList<PProblem>();
44 private List<PAnnotation> annotations = new ArrayList<PAnnotation>();
45 private QueryEvaluationHint evaluationHints = new QueryEvaluationHint(null, (IQueryBackendFactory)null);
46 PDisjunction canonicalDisjunction;
47 private List<String> parameterNames = null; // Lazy initialization
48
49 /** For traceability only. */
50 private List<Object> wrappingQuerySpecifications = new ArrayList<Object>(1);
51
52 @Override
53 public Integer getPositionOfParameter(String parameterName) {
54 ensureInitialized();
55 int index = getParameterNames().indexOf(parameterName);
56 return index != -1 ? index : null;
57 }
58
59 protected void setStatus(PQueryStatus newStatus) {
60 this.status = newStatus;
61 }
62
63 protected void addError(PProblem problem) {
64 status = PQueryStatus.ERROR;
65 pProblems.add(problem);
66 }
67
68 @Override
69 public PQueryStatus getStatus() {
70 return status;
71 }
72
73 @Override
74 public List<PProblem> getPProblems() {
75 return Collections.unmodifiableList(pProblems);
76 }
77
78 @Override
79 public boolean isMutable() {
80 return status.equals(PQueryStatus.UNINITIALIZED) || status.equals(PQueryStatus.INITIALIZING);
81 }
82
83 @Override
84 public void checkMutability() {
85 Preconditions.checkState(isMutable(), "Cannot edit query definition %s", getFullyQualifiedName());
86 }
87
88 /**
89 * @since 1.5
90 */
91 public void setEvaluationHints(QueryEvaluationHint hints) {
92 checkMutability();
93 this.evaluationHints = hints;
94 }
95
96 @Override
97 public QueryEvaluationHint getEvaluationHints() {
98 ensureInitialized();
99 return evaluationHints;
100 // TODO instead of field, compute something from annotations?
101 }
102
103 protected void addAnnotation(PAnnotation annotation) {
104 checkMutability();
105 annotations.add(annotation);
106 }
107
108 @Override
109 public List<PAnnotation> getAllAnnotations() {
110 ensureInitialized();
111 return new ArrayList<>(annotations);
112 }
113
114 private Stream<PAnnotation> getAnnotationStreamByName(final String name) {
115 ensureInitialized();
116 return annotations.stream().filter(Objects::nonNull).filter(annotation -> Objects.equals(name, annotation.getName()));
117 }
118
119 @Override
120 public List<PAnnotation> getAnnotationsByName(final String annotationName) {
121 return getAnnotationStreamByName(annotationName).collect(Collectors.toList());
122 }
123
124 @Override
125 public Optional<PAnnotation> getFirstAnnotationByName(String annotationName) {
126 return getAnnotationStreamByName(annotationName).findFirst();
127 }
128
129 @Override
130 public List<String> getParameterNames() {
131 ensureInitialized();
132 if (parameterNames == null) {
133 parameterNames = getParameters().stream().map(PParameter::getName).collect(Collectors.toList());
134 }
135 return parameterNames;
136 }
137
138 @Override
139 public Set<PQuery> getDirectReferredQueries() {
140 ensureInitialized();
141 return canonicalDisjunction.getDirectReferredQueries();
142 }
143
144 @Override
145 public Set<PQuery> getAllReferredQueries() {
146 ensureInitialized();
147 return canonicalDisjunction.getAllReferredQueries();
148 }
149
150
151 @Override
152 public List<Object> publishedAs() {
153 return wrappingQuerySpecifications;
154 }
155
156 @Override
157 public Set<TypeJudgement> getTypeGuarantees() {
158 ensureInitialized();
159 Set<TypeJudgement> result = new HashSet<TypeJudgement>();
160
161 List<PParameter> parameters = getParameters();
162 for (int i=0; i<parameters.size(); ++i) {
163 PParameter parameter = parameters.get(i);
164 IInputKey declaredUnaryType = parameter.getDeclaredUnaryType();
165 if (declaredUnaryType != null) {
166 result.add(new TypeJudgement(declaredUnaryType, Tuples.staticArityFlatTupleOf(i)));
167 }
168 }
169
170 return result;
171 }
172
173 /**
174 * @since 2.0
175 */
176 public BasePQuery(PVisibility visibility) {
177 super();
178 this.visibility = visibility;
179 }
180
181 @Override
182 public PDisjunction getDisjunctBodies() {
183 ensureInitialized();
184 Preconditions.checkState(!status.equals(PQueryStatus.ERROR), "Query %s contains errors.", getFullyQualifiedName());
185 return canonicalDisjunction;
186 }
187
188 @Override
189 public final void ensureInitialized() {
190 try {
191 if (status.equals(PQueryStatus.UNINITIALIZED)) {
192 setStatus(PQueryStatus.INITIALIZING);
193 setBodies(doGetContainedBodies());
194 setStatus(PQueryStatus.OK);
195 }
196 } catch (QueryInitializationException e) {
197 addError(new PProblem(e, e.getShortMessage()));
198 throw e;
199 }
200 }
201
202 protected final void setBodies(Set<PBody> bodies) {
203 canonicalDisjunction = new PDisjunction(this, bodies);
204 for (PBody body : canonicalDisjunction.getBodies()) {
205 body.setStatus(null);
206 }
207 setStatus(PQueryStatus.OK);
208 }
209
210 /**
211 * Creates and returns the bodies of the query. If recalled again, a new instance is created.
212 *
213 * @return
214 * @throws ViatraQueryRuntimeException
215 */
216 protected abstract Set<PBody> doGetContainedBodies();
217
218 @Override
219 public String toString() {
220 return String.format("PQuery<%s>=%s", getFullyQualifiedName(), super.toString());
221 }
222
223 /**
224 * @since 2.0
225 */
226 @Override
227 public PVisibility getVisibility() {
228 return visibility;
229 }
230
231} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java
new file mode 100644
index 00000000..eae4eacf
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.Collections;
12import java.util.LinkedHashSet;
13import java.util.Set;
14import java.util.stream.Collectors;
15
16import tools.refinery.viatra.runtime.matchers.psystem.PBody;
17
18/**
19 *
20 * A disjunction is a set of bodies representing separate conditions. A {@link PQuery} has a single, canonical
21 * PDisjunction, that can be replaced using rewriter
22 *
23 * @author Zoltan Ujhelyi
24 *
25 */
26public class PDisjunction {
27
28 private Set<PBody> bodies;
29 private PQuery query;
30
31 public PDisjunction(Set<PBody> bodies) {
32 this(bodies.iterator().next().getPattern(), bodies);
33 }
34
35 public PDisjunction(PQuery query, Set<PBody> bodies) {
36 super();
37 this.query = query;
38 this.bodies = Collections.unmodifiableSet(new LinkedHashSet<>(bodies));
39 this.bodies.forEach(body -> body.setContainerDisjunction(this));
40 }
41
42 /**
43 * Returns an immutable set of bodies that consists of this disjunction
44 *
45 * @return the bodies
46 */
47 public Set<PBody> getBodies() {
48 return bodies;
49 }
50
51 /**
52 * Returns the corresponding query specification. May be null if not set.
53 */
54 public PQuery getQuery() {
55 return query;
56 }
57
58 /**
59 * Returns all queries directly referred in the constraints. They are all required to evaluate this query
60 *
61 * @return a non-null, but possibly empty list of query definitions
62 */
63 public Set<PQuery> getDirectReferredQueries() {
64 return this.getBodies().stream().
65 flatMap(PQueries.directlyReferencedQueriesFunction()). // flatten stream of streams
66 collect(Collectors.toCollection(LinkedHashSet::new));
67 }
68
69 /**
70 * Returns all queries required to evaluate this query (transitively).
71 *
72 * @return a non-null, but possibly empty list of query definitions
73 */
74 public Set<PQuery> getAllReferredQueries() {
75 Set<PQuery> processedQueries = new LinkedHashSet<>();
76 processedQueries.add(this.getQuery());
77 Set<PQuery> foundQueries = getDirectReferredQueries();
78 Set<PQuery> newQueries = new LinkedHashSet<>(foundQueries);
79
80 while(!processedQueries.containsAll(newQueries)) {
81 PQuery query = newQueries.iterator().next();
82 processedQueries.add(query);
83 newQueries.remove(query);
84 Set<PQuery> referred = query.getDirectReferredQueries();
85 referred.removeAll(processedQueries);
86 foundQueries.addAll(referred);
87 newQueries.addAll(referred);
88 }
89 return foundQueries;
90 }
91
92 /**
93 * Decides whether a disjunction is mutable. A disjunction is mutable if all its contained bodies are mutable.
94 *
95 */
96 public boolean isMutable() {
97 for (PBody body : bodies) {
98 if (!body.isMutable()) {
99 return false;
100 }
101 }
102 return true;
103 }
104}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java
new file mode 100644
index 00000000..07165aa2
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.Objects;
12
13import tools.refinery.viatra.runtime.matchers.context.IInputKey;
14
15/**
16 * A descriptor for declared PQuery parameters. A parameter has a name, a declared type and a direction constraint
17 *
18 * @author Zoltan Ujhelyi
19 *
20 */
21public class PParameter {
22
23 private final String name;
24 private final String typeName;
25 private final IInputKey declaredUnaryType;
26 private final PParameterDirection direction;
27
28 public PParameter(String name) {
29 this(name, (String) null);
30 }
31
32 public PParameter(String name, String typeName) {
33 this(name, typeName, (IInputKey) null);
34 }
35
36 public PParameter(String name, String typeName, IInputKey declaredUnaryType) {
37 this(name, typeName, declaredUnaryType, PParameterDirection.INOUT);
38 }
39
40 /**
41 * @since 1.4
42 */
43 public PParameter(String name, String typeName, IInputKey declaredUnaryType, PParameterDirection direction) {
44 super();
45 this.name = name;
46 this.typeName = typeName;
47 this.declaredUnaryType = declaredUnaryType;
48 this.direction = direction;
49
50 if (declaredUnaryType != null && declaredUnaryType.getArity() != 1) {
51 throw new IllegalArgumentException(
52 "PParameter declared type must be unary instead of " + declaredUnaryType.getPrettyPrintableName());
53 }
54 }
55
56 /**
57 * @return the direction
58 * @since 1.4
59 */
60 public PParameterDirection getDirection() {
61 return direction;
62 }
63
64 /**
65 * @return the name of the parameter
66 */
67 public String getName() {
68 return name;
69 }
70
71 /**
72 * Returns a textual representation of the declared type of the parameter
73 *
74 * @return the type description, or null if not available
75 */
76 public String getTypeName() {
77 return typeName;
78 }
79
80 /**
81 * Yield an {@link IInputKey} representation of the type declared for this parameter.
82 *
83 * @return the unary type that was declared on this parameter in the query header, or null if not available
84 */
85 public IInputKey getDeclaredUnaryType() {
86 return declaredUnaryType;
87 }
88
89 @Override
90 public boolean equals(Object obj) {
91 if (obj instanceof PParameter) {
92 return Objects.equals(name, ((PParameter) obj).name)
93 && Objects.equals(typeName, ((PParameter) obj).typeName)
94 && Objects.equals(declaredUnaryType, ((PParameter) obj).declaredUnaryType)
95 && Objects.equals(direction, ((PParameter) obj).direction);
96 }
97 return false;
98 }
99
100 @Override
101 public int hashCode() {
102 return Objects.hash(name, typeName, declaredUnaryType);
103 }
104
105}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java
new file mode 100644
index 00000000..c94d4797
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java
@@ -0,0 +1,35 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11/**
12 * Values of this enum describe a constraint to the calling of patterns regarding its parameters.
13 *
14 * @author Grill Balázs
15 * @since 1.4
16 *
17 */
18public enum PParameterDirection {
19
20 /**
21 * Default value, no additional constraint is applied
22 */
23 INOUT,
24
25 /**
26 * The parameters marked with this constraints shall be set to a value before calling the pattern
27 */
28 IN,
29
30 /**
31 * The parameters marked with this constraints shall not be set to a value before calling the pattern
32 */
33 OUT
34
35}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java
new file mode 100644
index 00000000..1fe4f541
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java
@@ -0,0 +1,68 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
12
13/**
14 * Represents an error that was detected while the {@link PQuery} object was built from a source.
15 * @author Bergmann Gabor
16 *
17 */
18public class PProblem {
19
20 private final String shortMessage;
21 private final String location;
22 private final Exception exception;
23
24 public PProblem(String shortMessage) {
25 this(null, shortMessage, null, null);
26 }
27 /**
28 * @since 2.0
29 */
30 public PProblem(String shortMessage, Integer line, Integer column) {
31 this(null, shortMessage, line, column);
32 }
33 public PProblem(QueryProcessingException exception) {
34 this(exception, exception.getShortMessage(), null, null);
35 }
36 public PProblem(Exception exception, String shortMessage) {
37 this(exception, shortMessage, null, null);
38 }
39
40 /**
41 * @since 2.0
42 */
43 public PProblem(Exception exception, String shortMessage, Integer line, Integer column) {
44 this.shortMessage = shortMessage;
45 this.exception = exception;
46 if (line == null) {
47 location = "Unspecified location";
48 } else if (column == null) {
49 location = String.format("Line %d", line);
50 } else {
51 location = String.format("Line %d Column %d", line, column);
52 }
53 }
54
55 public String getShortMessage() {
56 return shortMessage;
57 }
58 public Exception getException() {
59 return exception;
60 }
61 /**
62 * @since 2.0
63 */
64 public String getLocation() {
65 return location;
66 }
67
68}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java
new file mode 100644
index 00000000..56f8ca76
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java
@@ -0,0 +1,110 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.HashSet;
12import java.util.Set;
13import java.util.function.Function;
14import java.util.function.Predicate;
15import java.util.stream.Stream;
16
17import tools.refinery.viatra.runtime.matchers.context.IInputKey;
18import tools.refinery.viatra.runtime.matchers.psystem.IMultiQueryReference;
19import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.PBody;
21import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
22import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
23import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
24
25/**
26 * Utility class for using PQueries in functional/streaming collection operations effectively
27 *
28 * @author Zoltan Ujhelyi
29 *
30 */
31public final class PQueries {
32
33 /**
34 * Hidden constructor for utility class
35 */
36 private PQueries() {
37 }
38
39 /**
40 * Predicate checking for the status of selected queries
41 *
42 */
43 public static Predicate<PQuery> queryStatusPredicate(final PQueryStatus status) {
44 return query -> query.getStatus().equals(status);
45 }
46
47 /**
48 * Enumerates referred queries (without duplicates) for the given body
49 */
50 public static Function<PBody, Stream<PQuery>> directlyReferencedQueriesFunction() {
51 return body -> (body.getConstraintsOfType(IMultiQueryReference.class).stream()
52 .flatMap(e -> e.getReferredQueries().stream()).distinct());
53 }
54
55 /**
56 * Enumerates directly referred extensional relations (without duplicates) in the canonical form of the given query
57 *
58 * @param enumerablesOnly
59 * only enumerable type constraints are considered
60 * @since 2.0
61 */
62 public static Stream<IInputKey> directlyRequiredTypesOfQuery(PQuery query, boolean enumerablesOnly) {
63 return directlyRequiredTypesOfDisjunction(query.getDisjunctBodies(), enumerablesOnly);
64 }
65
66 /**
67 * Enumerates directly referred extensional relations (without duplicates) for the given formulation of a query.
68 *
69 * @param enumerablesOnly
70 * only enumerable type constraints are considered
71 * @since 2.0
72 */
73 public static Stream<IInputKey> directlyRequiredTypesOfDisjunction(PDisjunction disjunctBodies,
74 boolean enumerablesOnly) {
75 Class<? extends ITypeConstraint> filterClass = enumerablesOnly ? TypeConstraint.class : ITypeConstraint.class;
76 return disjunctBodies.getBodies().stream().flatMap(body -> body.getConstraintsOfType(filterClass).stream())
77 .map(constraint -> constraint.getEquivalentJudgement().getInputKey()).distinct();
78 }
79
80 /**
81 * @since 1.4
82 */
83 public static Predicate<PParameter> parameterDirectionPredicate(final PParameterDirection direction) {
84 return input -> input.getDirection() == direction;
85 }
86
87 /**
88 * Returns all {@link PTraceable}s contained in the given {@link PQuery}: itself, its bodies and their constraints.
89 *
90 * @since 1.6
91 */
92 public static Set<PTraceable> getTraceables(PQuery query) {
93 final Set<PTraceable> traceables = new HashSet<>();
94 traceables.add(query);
95 query.getDisjunctBodies().getBodies().forEach(body -> {
96 traceables.add(body);
97 body.getConstraints().forEach(traceables::add);
98 });
99 return traceables;
100 }
101
102 /**
103 * Calculates the simple name related from a given qualified name by finding the part after the last '.' character.
104 *
105 * @since 2.0
106 */
107 public static String calculateSimpleName(String qualifiedName) {
108 return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
109 }
110}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java
new file mode 100644
index 00000000..a909c650
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java
@@ -0,0 +1,154 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.List;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
15import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
16import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider;
17import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
18import tools.refinery.viatra.runtime.matchers.psystem.PBody;
19import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
20import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
21
22/**
23 * Internal representation of a query / graph pattern (using a constraint system formalism),
24 * to be interpreted by a query evaluator ({@link IQueryBackend}).
25 * End-users of VIATRA Query should access a query as an IQuerySpecification instead.
26 *
27 * <p>
28 * PQuerys are definitions of queries usable inside pattern descriptions. Such description always has (a non-null) name. The query
29 * itself is defined as a (non-empty) set of {@link PBody} instances, the result is the disjunction of the single
30 * {@link PBody} instances. </p>
31 * <p>
32 * A PQuery might be constructed from erroneous patterns or might be uninitialized - this is represented by its status.
33 *
34 * @author Zoltan Ujhelyi
35 * @since 0.8.0
36 * @noimplement This interface is not intended to be implemented by clients. Use {@link BasePQuery} as a base class instead.
37 */
38public interface PQuery extends PQueryHeader, PTraceable {
39
40 // TODO rewritten as / rewritten from traceability to PDisjunction?
41
42 /**
43 * @author Zoltan Ujhelyi
44 *
45 */
46 public enum PQueryStatus {
47 /**
48 * Marks that the query definition is not initialized
49 */
50 UNINITIALIZED,
51 /**
52 * Marks that the query definition is being initialized
53 * @since 1.4
54 */
55 INITIALIZING,
56 /**
57 * The query definition was successfully initialized
58 */
59 OK,
60 /**
61 * The query definition was initialized, but some issues were present
62 */
63 WARNING,
64 /**
65 * The query definition was not successfully initialized because of an error
66 */
67 ERROR
68 }
69
70 /**
71 * Returns all bodies associated with the query in their canonical form. If called multiple times, the same set with
72 * the same contents will be returned.
73 *
74 */
75 PDisjunction getDisjunctBodies();
76
77 /**
78 * Returns all queries directly referred in the constraints. They are all required to evaluate this query
79 *
80 * @return a non-null, but possibly empty list of query definitions
81 */
82 Set<PQuery> getDirectReferredQueries();
83
84 /**
85 * Returns all queries required to evaluate this query (transitively).
86 *
87 * @return a non-null, but possibly empty list of query definitions
88 */
89 Set<PQuery> getAllReferredQueries();
90
91 /**
92 * Returns the initialization status of the definition
93 *
94 */
95 PQueryStatus getStatus();
96
97 /**
98 * Returns a list describing the problems that were found in this query.
99 *
100 * <p> TODO: formulate invariant connecting {@link #getPProblems()} and {@link #getStatus()}.
101 *
102 * @return a non-null, but possibly empty list of problems
103 */
104 List<PProblem> getPProblems();
105
106 /**
107 * Before a modification operation is executed, a mutability check is performed (via the {@link #getStatus()}
108 * implementation, and in case of problems an {@link IllegalStateException} is thrown.
109 */
110 void checkMutability();
111
112 /**
113 * An option to check mutability of the query. It can be used to avoid getting an {@link IllegalStateException} by
114 * the execution of {@link #checkMutability()}.
115 *
116 * @return true if the query specification is still editable
117 */
118 boolean isMutable();
119
120 /**
121 * Optional hints regarding the query evaluation strategy, to be interpreted by the query engine.
122 * <p> To ensure the possibility of external overrides,
123 * the evaluation engine should not directly consult this field,
124 * but use an {@link IQueryBackendHintProvider} instead.
125 */
126 public QueryEvaluationHint getEvaluationHints();
127
128
129 /**
130 * Type information, expressed on query parameters, that all matches of the query are guaranteed to respect.
131 * <p> At the very minimum, this should include the declared types of the parameters.
132 * <p> The type judgement tuples shall contain the <i>parameter index</i>, NOT the {@link PParameter} object.
133 *
134 * @return a non-null set of type judgements that the query guarantees for its matches
135 */
136 public Set<TypeJudgement> getTypeGuarantees();
137
138 /**
139 * If the query definition is uninitialized, initializes it.
140 * @throws ViatraQueryRuntimeException if initialization of query specification fails
141 */
142 public abstract void ensureInitialized();
143
144 /**
145 * Returns the end-user query specification API objects that wrap this query.
146 *
147 * <p> Intended for traceability and debug purposes, not part of normal operation.
148 * Returned list is intended to be appended during query specification construction time.
149 *
150 * @return a non-null, but possibly empty list of query specification objects;
151 */
152 List<Object> publishedAs();
153
154} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java
new file mode 100644
index 00000000..f3671934
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import java.util.List;
12import java.util.Optional;
13
14import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation;
15
16/**
17 * Represents header information (metainfo) about a query.
18 * <p> To be implemented both by IQuerySpecifications intended for end users,
19 * and the internal query representation {@link PQuery}.
20 *
21 *
22 * @author Bergmann Gabor
23 * @since 0.9
24 */
25public interface PQueryHeader {
26
27 /**
28 * Identifies the pattern for which matchers can be instantiated.
29 */
30 public String getFullyQualifiedName();
31
32 /**
33 * Return the list of parameter names
34 *
35 * @return a non-null, but possibly empty list of parameter names
36 */
37 public List<String> getParameterNames();
38
39 /**
40 * Returns a list of parameter descriptions
41 *
42 * @return a non-null, but possibly empty list of parameter descriptions
43 */
44 public List<PParameter> getParameters();
45
46 /**
47 * Returns the index of a named parameter
48 *
49 * @param parameterName
50 * @return the index, or null of no such parameter is available
51 */
52 public Integer getPositionOfParameter(String parameterName);
53
54 /**
55 * Returns a parameter by name if exists
56 * @since 2.1
57 */
58 default Optional<PParameter> getParameter(String parameterName) {
59 return Optional.ofNullable(getPositionOfParameter(parameterName))
60 .map(getParameters()::get);
61 }
62
63 /**
64 * Returns the list of annotations specified for this query
65 *
66 * @return a non-null, but possibly empty list of annotations
67 */
68 public List<PAnnotation> getAllAnnotations();
69
70 /**
71 * Returns the list of annotations with a specified name
72 *
73 * @param annotationName
74 * @return a non-null, but possibly empty list of annotations
75 */
76 public List<PAnnotation> getAnnotationsByName(String annotationName);
77
78 /**
79 * Returns the first annotation with a specified name
80 *
81 * @since 2.0
82 */
83 public Optional<PAnnotation> getFirstAnnotationByName(String annotationName);
84
85 /**
86 * Returns the visibility information about the query.
87 * @since 2.0
88 */
89 public PVisibility getVisibility();
90
91 /**
92 * Returns the non-qualified name of the query. By default this means returning the qualified name after the last
93 * '.' character.
94 *
95 * @since 2.0
96 */
97 public default String getSimpleName() {
98 return PQueries.calculateSimpleName(getFullyQualifiedName());
99 }
100
101} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java
new file mode 100644
index 00000000..7cb312bd
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java
@@ -0,0 +1,37 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11/**
12 * @author Zoltan Ujhelyi
13 * @since 2.0
14 *
15 */
16public enum PVisibility {
17
18 /**
19 * A public (default) visibility means a pattern can be called at any time.
20 */
21 PUBLIC,
22 /**
23 * A private query is not expected to be called directly, only by a different query matcher.
24 */
25 PRIVATE,
26 /**
27 * A query that is only used inside a single caller query and is not visible outside its container query. Such
28 * patterns must also fulfill the following additional constraints:
29 *
30 * <ul>
31 * <li>An embedded query must have only a single body.</li>
32 * <li>An embedded query must not be recursice.</li>
33 * </ul>
34 */
35 EMBEDDED
36
37}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java
new file mode 100644
index 00000000..470d7287
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.queries;
10
11import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
12
13/**
14 * Represent an exception that occurred while initializing the specification of a query.
15 * @author Bergmann Gabor
16 * @since 0.9
17 *
18 */
19public class QueryInitializationException extends QueryProcessingException {
20
21 public QueryInitializationException(String message, String[] context, String shortMessage, Object patternDescription,
22 Throwable cause) {
23 super(message, context, shortMessage, patternDescription, cause);
24 }
25
26 public QueryInitializationException(String message, String[] context, String shortMessage, Object patternDescription) {
27 super(message, context, shortMessage, patternDescription);
28 }
29
30 private static final long serialVersionUID = 9106033062252951489L;
31
32
33
34
35}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java
new file mode 100644
index 00000000..276b2b42
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java
@@ -0,0 +1,53 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.Objects;
12
13import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
14import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
15
16/**
17 * @since 1.6
18 *
19 */
20public class AbstractRewriterTraceSource {
21
22 private IRewriterTraceCollector traceCollector = NopTraceCollector.INSTANCE;
23
24 public void setTraceCollector(IRewriterTraceCollector traceCollector) {
25 this.traceCollector = Objects.requireNonNull(traceCollector);
26 }
27
28 public IPTraceableTraceProvider getTraces() {
29 return traceCollector;
30 }
31
32 protected IRewriterTraceCollector getTraceCollector() {
33 return traceCollector;
34 }
35
36 /**
37 * Mark the given derivative to be originated from the given original constraint.
38 * @since 1.6
39 */
40 protected void addTrace(PTraceable original, PTraceable derivative){
41 traceCollector.addTrace(original, derivative);
42 }
43
44 /**
45 * Indicate that the given derivative is removed from the resulting query, thus its trace
46 * information should be removed also.
47 * @since 1.6
48 */
49 protected void derivativeRemoved(PConstraint derivative, IDerivativeModificationReason reason){
50 traceCollector.derivativeRemoved(derivative, reason);
51 }
52
53}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java
new file mode 100644
index 00000000..237a762d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11/**
12 * Common reasons for removing constraint through rewriters
13 *
14 * @noreference This enum is not intended to be referenced by clients.
15 */
16public enum ConstraintRemovalReason implements IDerivativeModificationReason {
17
18 MOOT_EQUALITY,
19 WEAK_INEQUALITY_SELF_LOOP,
20 TYPE_SUBSUMED,
21 DUPLICATE
22
23}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java
new file mode 100644
index 00000000..3b5d7390
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
11
12/**
13 * @author Marton Bur
14 *
15 */
16public class DefaultFlattenCallPredicate implements IFlattenCallPredicate {
17
18 @Override
19 public boolean shouldFlatten(PositivePatternCall positivePatternCall) {
20 return true;
21 }
22
23}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java
new file mode 100644
index 00000000..06b8d372
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java
@@ -0,0 +1,129 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Objects;
16import java.util.Set;
17import java.util.stream.Collectors;
18
19import tools.refinery.viatra.runtime.matchers.psystem.PBody;
20import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
21import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
22import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality;
23import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
24import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation;
25import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
26import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
27import tools.refinery.viatra.runtime.matchers.util.Preconditions;
28
29/**
30 * This rewriter class can add new equality constraints to the copied body
31 *
32 * @author Marton Bur
33 *
34 */
35class FlattenerCopier extends PBodyCopier {
36
37 private final Map<PositivePatternCall, CallInformation> calls;
38
39 private static class CallInformation {
40 final PBody body;
41 final Map<PVariable, PVariable> variableMapping;
42
43 private CallInformation(PBody body) {
44 this.body = body;
45 this.variableMapping = new HashMap<>();
46 }
47 }
48
49 public FlattenerCopier(PQuery query, Map<PositivePatternCall, PBody> callsToFlatten) {
50 super(query);
51 this.calls = callsToFlatten.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> new CallInformation(entry.getValue())));
52 }
53
54 protected void copyVariable(PositivePatternCall contextPatternCall, PVariable variable, String newName) {
55 PVariable newPVariable = body.getOrCreateVariableByName(newName);
56 calls.get(contextPatternCall).variableMapping.put(variable, newPVariable);
57 variableMapping.put(variable, newPVariable);
58 }
59
60 /**
61 * Merge all variables and constraints from the body called through the given pattern call to a target body. If
62 * multiple bodies are merged into a single one, use the renamer and filter options to avoid collisions.
63 *
64 * @param sourceBody
65 * @param namingTool
66 * @param filter
67 */
68 public void mergeBody(PositivePatternCall contextPatternCall, IVariableRenamer namingTool,
69 IConstraintFilter filter) {
70
71 PBody sourceBody = calls.get(contextPatternCall).body;
72
73 // Copy variables
74 Set<PVariable> allVariables = sourceBody.getAllVariables();
75 for (PVariable pVariable : allVariables) {
76 if (pVariable.isUnique()) {
77 copyVariable(contextPatternCall, pVariable,
78 namingTool.createVariableName(pVariable, sourceBody.getPattern()));
79 }
80 }
81
82 // Copy constraints which are not filtered
83 Set<PConstraint> constraints = sourceBody.getConstraints();
84 for (PConstraint pConstraint : constraints) {
85 if (!(pConstraint instanceof ExportedParameter) && !filter.filter(pConstraint)) {
86 copyConstraint(pConstraint);
87 }
88 }
89 }
90
91 @Override
92 protected void copyPositivePatternCallConstraint(PositivePatternCall positivePatternCall) {
93
94 if (!calls.containsKey(positivePatternCall)) {
95 // If the call was not flattened, copy the constraint
96 super.copyPositivePatternCallConstraint(positivePatternCall);
97 } else {
98 PBody calledBody = Objects.requireNonNull(calls.get(positivePatternCall).body);
99 Preconditions.checkArgument(positivePatternCall.getReferredQuery().equals(calledBody.getPattern()));
100
101 List<PVariable> symbolicParameters = calledBody.getSymbolicParameterVariables();
102 Object[] elements = positivePatternCall.getVariablesTuple().getElements();
103 for (int i = 0; i < elements.length; i++) {
104 // Create equality constraints between the caller PositivePatternCall and the corresponding body
105 // parameter variables
106 createEqualityConstraint((PVariable) elements[i], symbolicParameters.get(i), positivePatternCall);
107 }
108
109 }
110 }
111
112 private void createEqualityConstraint(PVariable pVariable1, PVariable pVariable2,
113 PositivePatternCall contextPatternCall) {
114 PVariable who = variableMapping.get(pVariable1);
115 PVariable withWhom = calls.get(contextPatternCall).variableMapping.get(pVariable2);
116 addTrace(contextPatternCall, new Equality(body, who, withWhom));
117 }
118
119 @Override
120 protected void copyExpressionEvaluationConstraint(final ExpressionEvaluation expressionEvaluation) {
121 Map<PVariable, PVariable> variableMapping = this.variableMapping.entrySet().stream()
122 .filter(input -> expressionEvaluation.getPSystem().getAllVariables().contains(input.getKey()))
123 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
124
125 PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable());
126 addTrace(expressionEvaluation, new ExpressionEvaluation(body, new VariableMappingExpressionEvaluatorWrapper(expressionEvaluation.getEvaluator(), variableMapping), mappedOutputVariable, expressionEvaluation.isUnwinding()));
127 }
128
129}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java
new file mode 100644
index 00000000..518b9c64
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
12import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter;
13
14/**
15 * Helper interface to exclude constraints from PBody copy processes
16 *
17 * @author Marton Bur
18 *
19 */
20public interface IConstraintFilter {
21 /**
22 * Returns true, if the given constraint should be filtered (thus should not be copied)
23 *
24 * @param constraint
25 * to check
26 * @return true, if the constraint should be filtered
27 */
28 boolean filter(PConstraint constraint);
29
30 public static class ExportedParameterFilter implements IConstraintFilter {
31
32 @Override
33 public boolean filter(PConstraint constraint) {
34 return constraint instanceof ExportedParameter;
35 }
36
37 }
38
39 public static class AllowAllFilter implements IConstraintFilter {
40
41 @Override
42 public boolean filter(PConstraint constraint) {
43 // Nothing is filtered
44 return false;
45 }
46
47 }
48} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java
new file mode 100644
index 00000000..dbd6a78d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java
@@ -0,0 +1,19 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11/**
12 * This is a role indication interface, implementations may provide a reason about
13 * why a modification is made during PQuery normalization.
14 * @since 1.6
15 *
16 */
17public interface IDerivativeModificationReason {
18
19}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java
new file mode 100644
index 00000000..7e224e98
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
12
13
14/**
15 * Interface used by the PQueryFlattener to decide which positive pattern calls to flatten
16 *
17 * @author Marton Bur
18 *
19 */
20public interface IFlattenCallPredicate {
21
22 /**
23 * Decides whether the called query by the pattern call should be flattened into the caller or not.
24 *
25 * @param positivePatternCall
26 * the pattern call
27 * @return true if the call should be flattened
28 */
29 boolean shouldFlatten(PositivePatternCall positivePatternCall);
30
31 /**
32 * Flattens only if all operand predicates vote for flattening.
33 * @author Gabor Bergmann
34 * @since 2.1
35 */
36 public static class And implements IFlattenCallPredicate {
37 private IFlattenCallPredicate[] operands;
38 public And(IFlattenCallPredicate... operands) {
39 this.operands = operands;
40 }
41
42 @Override
43 public boolean shouldFlatten(PositivePatternCall positivePatternCall) {
44 for (IFlattenCallPredicate operand : operands) {
45 if (!operand.shouldFlatten(positivePatternCall)) return false;
46 }
47 return true;
48 }
49 }
50}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java
new file mode 100644
index 00000000..84da4d1b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15
16/**
17 * This interface provides methods to trace the {@link PTraceable}s of a transformed {@link PQuery} produced by
18 * a {@link PDisjunctionRewriter}. In case the associated rewriter is a composite (a.k.a. {@link PDisjunctionRewriterCacher}),
19 * this trace provider handles traces end-to-end, hiding all the intermediate transformation steps.
20 *
21 * @since 1.6
22 * @noimplement This interface is not intended to be implemented by clients.
23 */
24public interface IPTraceableTraceProvider {
25
26 /**
27 * Find and return the canonical {@link PTraceable}s in the original query which are the sources of the given derivative
28 * {@link PTraceable} according to the transformation.
29 *
30 * @param derivative a {@link PTraceable} which is contained by the {@link PQuery} produced by the associated rewriter
31 * @since 2.0
32 */
33 public Stream<PTraceable> getCanonicalTraceables(PTraceable derivative);
34
35 /**
36 * Find and return the {@link PTraceable}s in the rewritten query which are the destinations of the given source
37 * {@link PTraceable} according to the transformation.
38 *
39 * @param source a {@link PTraceable} which is contained by a {@link PQuery} before rewriting
40 * @since 2.0
41 */
42 public Stream<PTraceable> getRewrittenTraceables(PTraceable source);
43
44 /**
45 * Returns whether the given traceable element has been removed by every rewriter for a reason.
46 */
47 public boolean isRemoved(PTraceable traceable);
48
49 /**
50 * Returns the reasons for which the traceable element has been removed by the rewriters.
51 * @return the reasons of removal during rewriting
52 * @since 2.0
53 */
54 public Stream<IDerivativeModificationReason> getRemovalReasons(PTraceable traceable);
55}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java
new file mode 100644
index 00000000..70771ea7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
12
13/**
14 * This is the internal API of {@link IPTraceableTraceProvider} expected to be used by
15 * copier and rewriter implementations.
16 *
17 * @since 1.6
18 * @noreference This interface is not intended to be referenced by clients.
19 */
20public interface IRewriterTraceCollector extends IPTraceableTraceProvider {
21
22 /**
23 * Mark the given derivative to be originated from the given original constraint.
24 */
25 public void addTrace(PTraceable origin, PTraceable derivative);
26
27 /**
28 * Indicate that the given derivative is removed from the resulting query, thus its trace
29 * information should be removed also.
30 */
31 public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason);
32
33}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java
new file mode 100644
index 00000000..ce446e0d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java
@@ -0,0 +1,59 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
12import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
13
14/**
15 * Helper interface to ease the naming of the new variables during flattening
16 *
17 * @author Marton Bur
18 *
19 */
20public interface IVariableRenamer {
21 /**
22 * Creates a variable name based on a given variable and a given query. It only creates a String, doesn't set
23 * anything.
24 *
25 * @param pVariable
26 * @param query
27 * @return the new variable name as a String
28 */
29 String createVariableName(PVariable pVariable, PQuery query);
30
31 public class SameName implements IVariableRenamer {
32 @Override
33 public String createVariableName(PVariable pVariable, PQuery query) {
34 return pVariable.getName();
35 }
36 }
37
38 public class HierarchicalName implements IVariableRenamer {
39
40 private int callCount;
41
42 public void setCallCount(int callCount) {
43 this.callCount = callCount;
44 }
45
46 @Override
47 public String createVariableName(PVariable pVariable, PQuery query) {
48 // make sure to keep the "_" prefix before anonymous variables
49 String newVarName = getShortName(query) + "<" + callCount + ">" + "_" + pVariable.getName();
50 return pVariable.getName().startsWith("_") ? "_" + newVarName : newVarName ;
51 }
52
53 private String getShortName(PQuery query) {
54 String fullyQualifiedName = query.getFullyQualifiedName();
55 int beginIndex = fullyQualifiedName.lastIndexOf('.') + 1;
56 return fullyQualifiedName.substring(beginIndex);
57 }
58 }
59} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java
new file mode 100644
index 00000000..7429fc60
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java
@@ -0,0 +1,135 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.Queue;
17import java.util.Set;
18import java.util.function.Predicate;
19import java.util.stream.Stream;
20
21import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
23import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
24import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
25import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
26import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
27import tools.refinery.viatra.runtime.matchers.util.Preconditions;
28
29/**
30 * Multimap-based implementation to contain and query traces
31 *
32 * @since 1.6
33 *
34 */
35public class MappingTraceCollector implements IRewriterTraceCollector {
36
37 /**
38 * Traces from derivative to original
39 */
40 private final IMultiLookup<PTraceable, PTraceable> traces = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
41
42 /**
43 * Traces from original to derivative
44 */
45 private final IMultiLookup<PTraceable, PTraceable> inverseTraces = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
46
47 /**
48 * Reasons for removing {@link PTraceable}s
49 */
50 private final Map<PTraceable, IDerivativeModificationReason> removals = new HashMap<>();
51
52 /**
53 * Decides whether {@link PTraceable} is removed
54 */
55 private final Predicate<PTraceable> removed = removals::containsKey;
56
57 /**
58 * @since 2.0
59 */
60 @Override
61 public Stream<PTraceable> getCanonicalTraceables(PTraceable derivative) {
62 return findTraceEnds(derivative, traces).stream();
63 }
64
65 /**
66 * @since 2.0
67 */
68 @Override
69 public Stream<PTraceable> getRewrittenTraceables(PTraceable source) {
70 return findTraceEnds(source, inverseTraces).stream();
71 }
72
73 /**
74 * Returns the end of trace chains starting from the given {@link PTraceable} along the given trace edges.
75 */
76 private Set<PTraceable> findTraceEnds(PTraceable traceable, IMultiLookup<PTraceable, PTraceable> traceRecords) {
77 if (traceable instanceof PQuery) { // PQueries are preserved
78 return Collections.singleton(traceable);
79 }
80 Set<PTraceable> visited = new HashSet<>();
81 Set<PTraceable> result = new HashSet<>();
82 Queue<PTraceable> queue = new LinkedList<>();
83 queue.add(traceable);
84 while(!queue.isEmpty()){
85 PTraceable aDerivative = queue.poll();
86 // Track visited elements to avoid infinite loop via directed cycles in traces
87 visited.add(aDerivative);
88 IMemoryView<PTraceable> nextOrigins = traceRecords.lookup(aDerivative);
89 if (nextOrigins == null){
90 // End of trace chain
91 result.add(aDerivative);
92 } else {
93 // Follow traces
94 for(PTraceable nextOrigin : nextOrigins){
95 if (!visited.contains(nextOrigin)){
96 queue.add(nextOrigin);
97 }
98 }
99 }
100 }
101 return result;
102 }
103
104 @Override
105 public void addTrace(PTraceable original, PTraceable derivative){
106 traces.addPairOrNop(derivative, original);
107 inverseTraces.addPairOrNop(original, derivative);
108 // Even if this element was marked as removed earlier, now we replace it with another constraint!
109 removals.remove(original);
110 }
111
112 @Override
113 public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason){
114 Preconditions.checkState(!removals.containsKey(derivative), "Traceable %s removed multiple times", derivative);
115 // XXX the derivative must not be removed from the trace chain, as some rewriters, e.g. the normalizer keeps trace links to deleted elements
116 if (!inverseTraces.lookupExists(derivative)) {
117 // If there already exists a trace link, this removal means an update
118 removals.put(derivative, reason);
119 }
120 }
121
122 @Override
123 public boolean isRemoved(PTraceable traceable) {
124 return getRewrittenTraceables(traceable).allMatch(removed);
125 }
126
127 /**
128 * @since 2.0
129 */
130 @Override
131 public Stream<IDerivativeModificationReason> getRemovalReasons(PTraceable traceable) {
132 return getRewrittenTraceables(traceable).filter(removed).map(removals::get);
133 }
134
135}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java
new file mode 100644
index 00000000..96c0b205
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
12
13/**
14 * @author Grill Balázs
15 * @since 1.4
16 *
17 */
18public class NeverFlattenCallPredicate implements IFlattenCallPredicate {
19
20
21 @Override
22 public boolean shouldFlatten(PositivePatternCall positivePatternCall) {
23 return false;
24 }
25
26}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java
new file mode 100644
index 00000000..15cf577e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java
@@ -0,0 +1,68 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.psystem.PTraceable;
14
15/**
16 * This implementation does not store any traces and scales to NOP for every traceability feature.
17 * @since 1.6
18 *
19 */
20public class NopTraceCollector implements IRewriterTraceCollector {
21
22 public static final IRewriterTraceCollector INSTANCE = new NopTraceCollector();
23
24 private NopTraceCollector() {
25 // Private constructor to force using the common instance
26 }
27
28 /**
29 * @since 2.0
30 */
31 @Override
32 public Stream<PTraceable> getCanonicalTraceables(PTraceable derivative) {
33 return Stream.empty();
34 }
35
36 /**
37 * @since 2.0
38 */
39 @Override
40 public Stream<PTraceable> getRewrittenTraceables(PTraceable source) {
41 return Stream.empty();
42 }
43
44
45 @Override
46 public void addTrace(PTraceable origin, PTraceable derivative) {
47 // ignored
48 }
49
50 @Override
51 public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason) {
52 // ignored
53 }
54
55 @Override
56 public boolean isRemoved(PTraceable traceable) {
57 return false;
58 }
59
60 /**
61 * @since 2.0
62 */
63 @Override
64 public Stream<IDerivativeModificationReason> getRemovalReasons(PTraceable traceable) {
65 return Stream.empty();
66 }
67
68}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java
new file mode 100644
index 00000000..10ab19c8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java
@@ -0,0 +1,307 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools>
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0 which is available at
6 * http://www.eclipse.org/legal/epl-v20.html.
7 *
8 * SPDX-License-Identifier: EPL-2.0
9 *******************************************************************************/
10package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
11
12import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
13import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint;
14import tools.refinery.viatra.runtime.matchers.psystem.PBody;
15import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
16import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
17import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*;
18import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*;
19import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
20import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
21import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.AllowAllFilter;
22import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.SameName;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
25
26import java.util.*;
27import java.util.stream.Collectors;
28
29/**
30 * This class can create a new PBody for a PQuery. The result body contains a copy of given variables and constraints.
31 *
32 * @author Marton Bur
33 *
34 */
35public class PBodyCopier extends AbstractRewriterTraceSource {
36
37 /**
38 * The created body
39 */
40 protected PBody body;
41 /**
42 * Mapping between the original and the copied variables
43 */
44 protected Map<PVariable, PVariable> variableMapping = new HashMap<>();
45
46 public Map<PVariable, PVariable> getVariableMapping() {
47 return variableMapping;
48 }
49
50 /**
51 * @since 1.6
52 */
53 public PBodyCopier(PBody body, IRewriterTraceCollector traceCollector) {
54 this.body = new PBody(body.getPattern());
55 setTraceCollector(traceCollector);
56
57 // do the actual copying
58 mergeBody(body);
59 }
60
61 /**
62 * @since 1.6
63 */
64 public PBodyCopier(PQuery query) {
65 this.body = new PBody(query);
66 }
67
68 public void mergeBody(PBody sourceBody) {
69 mergeBody(sourceBody, new SameName(), new AllowAllFilter());
70 }
71
72 /**
73 * Merge all variables and constraints from a source body to a target body. If multiple bodies are merged into a
74 * single one, use the renamer and filter options to avoid collisions.
75 */
76 public void mergeBody(PBody sourceBody, IVariableRenamer namingTool, IConstraintFilter filter) {
77
78 // Copy variables
79 Set<PVariable> allVariables = sourceBody.getAllVariables();
80 for (PVariable pVariable : allVariables) {
81 if (pVariable.isUnique()) {
82 copyVariable(pVariable, namingTool.createVariableName(pVariable, sourceBody.getPattern()));
83 }
84 }
85
86 // Copy exported parameters
87 this.body.setSymbolicParameters(sourceBody.getSymbolicParameters().stream()
88 .map(this::copyExportedParameterConstraint).collect(Collectors.toList()));
89
90 // Copy constraints which are not filtered
91 Set<PConstraint> constraints = sourceBody.getConstraints();
92 for (PConstraint pConstraint : constraints) {
93 if (!(pConstraint instanceof ExportedParameter) && !filter.filter(pConstraint)) {
94 copyConstraint(pConstraint);
95 }
96 }
97
98 // Add trace between original and copied body
99 addTrace(sourceBody, body);
100 }
101
102 protected void copyVariable(PVariable variable, String newName) {
103 PVariable newPVariable = body.getOrCreateVariableByName(newName);
104 variableMapping.put(variable, newPVariable);
105 }
106
107 /**
108 * Returns the body with the copied variables and constraints. The returned body is still uninitialized.
109 */
110 public PBody getCopiedBody() {
111 return body;
112 }
113
114 protected void copyConstraint(PConstraint constraint) {
115 if (constraint instanceof ExportedParameter) {
116 copyExportedParameterConstraint((ExportedParameter) constraint);
117 } else if (constraint instanceof Equality) {
118 copyEqualityConstraint((Equality) constraint);
119 } else if (constraint instanceof Inequality) {
120 copyInequalityConstraint((Inequality) constraint);
121 } else if (constraint instanceof TypeConstraint) {
122 copyTypeConstraint((TypeConstraint) constraint);
123 } else if (constraint instanceof TypeFilterConstraint) {
124 copyTypeFilterConstraint((TypeFilterConstraint) constraint);
125 } else if (constraint instanceof ConstantValue) {
126 copyConstantValueConstraint((ConstantValue) constraint);
127 } else if (constraint instanceof PositivePatternCall) {
128 copyPositivePatternCallConstraint((PositivePatternCall) constraint);
129 } else if (constraint instanceof NegativePatternCall) {
130 copyNegativePatternCallConstraint((NegativePatternCall) constraint);
131 } else if (constraint instanceof BinaryTransitiveClosure) {
132 copyBinaryTransitiveClosureConstraint((BinaryTransitiveClosure) constraint);
133 } else if (constraint instanceof RepresentativeElectionConstraint) {
134 copyRepresentativeElectionConstraint((RepresentativeElectionConstraint) constraint);
135 } else if (constraint instanceof RelationEvaluation) {
136 copyRelationEvaluationConstraint((RelationEvaluation) constraint);
137 } else if (constraint instanceof BinaryReflexiveTransitiveClosure) {
138 copyBinaryReflexiveTransitiveClosureConstraint((BinaryReflexiveTransitiveClosure) constraint);
139 } else if (constraint instanceof PatternMatchCounter) {
140 copyPatternMatchCounterConstraint((PatternMatchCounter) constraint);
141 } else if (constraint instanceof AggregatorConstraint) {
142 copyAggregatorConstraint((AggregatorConstraint) constraint);
143 } else if (constraint instanceof ExpressionEvaluation) {
144 copyExpressionEvaluationConstraint((ExpressionEvaluation) constraint);
145 } else {
146 throw new QueryProcessingException("Unknown PConstraint {0} encountered while copying PBody",
147 new String[] { constraint.getClass().getName() }, "Unknown PConstraint", body.getPattern());
148 }
149 }
150
151 protected ExportedParameter copyExportedParameterConstraint(ExportedParameter exportedParameter) {
152 PVariable mappedPVariable = variableMapping.get(exportedParameter.getParameterVariable());
153 PParameter parameter = exportedParameter.getPatternParameter();
154 ExportedParameter newExportedParameter;
155 newExportedParameter = new ExportedParameter(body, mappedPVariable, parameter);
156 body.getSymbolicParameters().add(newExportedParameter);
157 addTrace(exportedParameter, newExportedParameter);
158 return newExportedParameter;
159 }
160
161 protected void copyEqualityConstraint(Equality equality) {
162 PVariable who = equality.getWho();
163 PVariable withWhom = equality.getWithWhom();
164 addTrace(equality, new Equality(body, variableMapping.get(who), variableMapping.get(withWhom)));
165 }
166
167 protected void copyInequalityConstraint(Inequality inequality) {
168 PVariable who = inequality.getWho();
169 PVariable withWhom = inequality.getWithWhom();
170 addTrace(inequality, new Inequality(body, variableMapping.get(who), variableMapping.get(withWhom)));
171 }
172
173 protected void copyTypeConstraint(TypeConstraint typeConstraint) {
174 PVariable[] mappedVariables = extractMappedVariables(typeConstraint);
175 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
176 addTrace(typeConstraint, new TypeConstraint(body, variablesTuple, typeConstraint.getSupplierKey()));
177 }
178
179 protected void copyTypeFilterConstraint(TypeFilterConstraint typeConstraint) {
180 PVariable[] mappedVariables = extractMappedVariables(typeConstraint);
181 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
182 addTrace(typeConstraint, new TypeFilterConstraint(body, variablesTuple, typeConstraint.getInputKey()));
183 }
184
185 protected void copyConstantValueConstraint(ConstantValue constantValue) {
186 PVariable pVariable = (PVariable) constantValue.getVariablesTuple().getElements()[0];
187 addTrace(constantValue,
188 new ConstantValue(body, variableMapping.get(pVariable), constantValue.getSupplierKey()));
189 }
190
191 protected void copyPositivePatternCallConstraint(PositivePatternCall positivePatternCall) {
192 PVariable[] mappedVariables = extractMappedVariables(positivePatternCall);
193 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
194 addTrace(positivePatternCall,
195 new PositivePatternCall(body, variablesTuple, positivePatternCall.getReferredQuery()));
196 }
197
198 protected void copyNegativePatternCallConstraint(NegativePatternCall negativePatternCall) {
199 PVariable[] mappedVariables = extractMappedVariables(negativePatternCall);
200 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
201 addTrace(negativePatternCall,
202 new NegativePatternCall(body, variablesTuple, negativePatternCall.getReferredQuery()));
203 }
204
205 protected void copyBinaryTransitiveClosureConstraint(BinaryTransitiveClosure binaryTransitiveClosure) {
206 PVariable[] mappedVariables = extractMappedVariables(binaryTransitiveClosure);
207 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
208 addTrace(binaryTransitiveClosure,
209 new BinaryTransitiveClosure(body, variablesTuple, binaryTransitiveClosure.getReferredQuery()));
210 }
211
212 protected void copyRepresentativeElectionConstraint(RepresentativeElectionConstraint constraint) {
213 var mappedVariables = extractMappedVariables(constraint);
214 var variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
215 addTrace(constraint, new RepresentativeElectionConstraint(body, variablesTuple, constraint.getReferredQuery(),
216 constraint.getConnectivity()));
217 }
218
219 /**
220 * @since 2.8
221 */
222 protected void copyRelationEvaluationConstraint(RelationEvaluation relationEvaluation) {
223 PVariable[] mappedVariables = extractMappedVariables(relationEvaluation);
224 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
225 addTrace(relationEvaluation, new RelationEvaluation(body, variablesTuple, relationEvaluation.getReferredQueries(),
226 relationEvaluation.getEvaluator()));
227 }
228
229 /**
230 * @since 2.0
231 */
232 protected void copyBinaryReflexiveTransitiveClosureConstraint(
233 BinaryReflexiveTransitiveClosure binaryReflexiveTransitiveClosure) {
234 PVariable[] mappedVariables = extractMappedVariables(binaryReflexiveTransitiveClosure);
235 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
236 addTrace(binaryReflexiveTransitiveClosure,
237 new BinaryReflexiveTransitiveClosure(body, variablesTuple,
238 binaryReflexiveTransitiveClosure.getReferredQuery(),
239 binaryReflexiveTransitiveClosure.getUniverseType()));
240 }
241
242 protected void copyPatternMatchCounterConstraint(PatternMatchCounter patternMatchCounter) {
243 PVariable[] mappedVariables = extractMappedVariables(patternMatchCounter);
244 PVariable mappedResultVariable = variableMapping.get(patternMatchCounter.getResultVariable());
245 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
246 addTrace(patternMatchCounter, new PatternMatchCounter(body, variablesTuple,
247 patternMatchCounter.getReferredQuery(), mappedResultVariable));
248 }
249
250 /**
251 * @since 1.4
252 */
253 protected void copyAggregatorConstraint(AggregatorConstraint constraint) {
254 PVariable[] mappedVariables = extractMappedVariables(constraint);
255 PVariable mappedResultVariable = variableMapping.get(constraint.getResultVariable());
256 Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables);
257 addTrace(constraint, new AggregatorConstraint(constraint.getAggregator(), body, variablesTuple,
258 constraint.getReferredQuery(), mappedResultVariable, constraint.getAggregatedColumn()));
259 }
260
261 protected void copyExpressionEvaluationConstraint(ExpressionEvaluation expressionEvaluation) {
262 PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable());
263 addTrace(expressionEvaluation, new ExpressionEvaluation(body,
264 new VariableMappingExpressionEvaluatorWrapper(expressionEvaluation.getEvaluator(), variableMapping),
265 mappedOutputVariable, expressionEvaluation.isUnwinding()));
266 }
267
268 /**
269 * For positive pattern calls
270 *
271 * @param positivePatternCall
272 * @return the mapped variables to the pattern's parameters
273 */
274 protected PVariable[] extractMappedVariables(EnumerablePConstraint enumerablePConstraint) {
275 Object[] pVariables = enumerablePConstraint.getVariablesTuple().getElements();
276 return mapVariableList(pVariables);
277 }
278
279 /**
280 * For negative and count pattern calls.
281 *
282 * @param patternMatchCounter
283 * @return the mapped variables to the pattern's parameters
284 */
285 private PVariable[] extractMappedVariables(PatternCallBasedDeferred patternCallBasedDeferred) {
286 Object[] pVariables = patternCallBasedDeferred.getActualParametersTuple().getElements();
287 return mapVariableList(pVariables);
288 }
289
290 /**
291 * For type filters.
292 */
293 private PVariable[] extractMappedVariables(TypeFilterConstraint typeFilterConstraint) {
294 Object[] pVariables = typeFilterConstraint.getVariablesTuple().getElements();
295 return mapVariableList(pVariables);
296 }
297
298 private PVariable[] mapVariableList(Object[] pVariables) {
299 List<PVariable> list = new ArrayList<PVariable>();
300 for (int i = 0; i < pVariables.length; i++) {
301 PVariable mappedVariable = variableMapping.get(pVariables[i]);
302 list.add(mappedVariable);
303 }
304 return list.toArray(new PVariable[0]);
305 }
306
307}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java
new file mode 100644
index 00000000..90943129
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java
@@ -0,0 +1,310 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
11
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.Comparator;
16import java.util.HashMap;
17import java.util.HashSet;
18import java.util.Iterator;
19import java.util.LinkedHashSet;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23import java.util.Queue;
24import java.util.Set;
25
26import tools.refinery.viatra.runtime.matchers.context.IInputKey;
27import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext;
28import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
29import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper;
30import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint;
31import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint;
32import tools.refinery.viatra.runtime.matchers.psystem.PBody;
33import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
34import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement;
35import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality;
36import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality;
37import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
38import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
39import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
40import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
41
42/**
43 * A disjunction rewriter for creating a normalized form of specification, unifying variables and running basic sanity
44 * checks. This rewriter does not copy but modifies directly the original specification, requiring a mutable
45 * disjunction.
46 *
47 * @author Gabor Bergmann
48 *
49 */
50public class PBodyNormalizer extends PDisjunctionRewriter {
51
52 private IQueryMetaContext context;
53
54 public PBodyNormalizer(IQueryMetaContext context) {
55 this.context = context;
56 }
57
58 /**
59 * Returns whether unary constraint elimination is enabled. This behavior can be customized by creating a subclass
60 * with a custom implementation.
61 *
62 * @since 1.6
63 */
64 protected boolean shouldCalculateImpliedTypes(PQuery query) {
65 return true;
66 }
67
68 /**
69 * Returns whether 'weakened alternative' suggestions of the context shall be expanded as additional PConstraints.
70 * This behavior can be customized by creating a subclass
71 * with a custom implementation.
72 *
73 * @since 1.6
74 */
75 protected boolean shouldExpandWeakenedAlternatives(PQuery query) {
76 return false;
77 }
78
79 @Override
80 public PDisjunction rewrite(PDisjunction disjunction) {
81 Set<PBody> normalizedBodies = new LinkedHashSet<>();
82 for (PBody body : disjunction.getBodies()) {
83 PBodyCopier copier = new PBodyCopier(body, getTraceCollector());
84 PBody modifiedBody = copier.getCopiedBody();
85 normalizeBody(modifiedBody);
86 normalizedBodies.add(modifiedBody);
87 modifiedBody.setStatus(PQueryStatus.OK);
88 }
89 return new PDisjunction(normalizedBodies);
90 }
91
92 public void setContext(IQueryMetaContext context) {
93 this.context = context;
94 }
95
96 /**
97 * Provides a normalized version of the pattern body. May return a different version than the original version if
98 * needed.
99 *
100 * @param body
101 */
102 public PBody normalizeBody(PBody body) {
103 try {
104 return normalizeBodyInternal(body);
105 } catch (QueryProcessingException e) {
106 throw new RewriterException("Error during rewriting: {1}", new String[] { e.getMessage() },
107 e.getShortMessage(), body.getPattern(), e);
108 }
109 }
110
111 PBody normalizeBodyInternal(PBody body) {
112 // UNIFICATION AND WEAK INEQUALITY ELIMINATION
113 unifyVariablesAlongEqualities(body);
114 eliminateWeakInequalities(body);
115 removeMootEqualities(body);
116
117 // ADDING WEAKENED ALTERNATIVES
118 if (shouldExpandWeakenedAlternatives(body.getPattern())) {
119 expandWeakenedAlternativeConstraints(body);
120 }
121
122 // CONSTRAINT ELIMINATION WITH TYPE INFERENCE
123 if (shouldCalculateImpliedTypes(body.getPattern())) {
124 eliminateInferrableTypes(body, context);
125 } else {
126 // ELIMINATE DUPLICATE TYPE CONSTRAINTS
127 eliminateDuplicateTypeConstraints(body);
128 }
129
130
131 // PREVENTIVE CHECKS
132 checkSanity(body);
133 return body;
134 }
135
136 private void removeMootEqualities(PBody body) {
137 Set<Equality> equals = body.getConstraintsOfType(Equality.class);
138 for (Equality equality : equals) {
139 if (equality.isMoot()) {
140 equality.delete();
141 derivativeRemoved(equality, ConstraintRemovalReason.MOOT_EQUALITY);
142 }
143 }
144 }
145
146 /**
147 * Unifies allVariables along equalities so that they can be handled as one.
148 *
149 * @param body
150 */
151 void unifyVariablesAlongEqualities(PBody body) {
152 Set<Equality> equals = body.getConstraintsOfType(Equality.class);
153 for (Equality equality : equals) {
154 if (!equality.isMoot()) {
155 equality.getWho().unifyInto(equality.getWithWhom());
156 }
157 }
158 }
159
160 /**
161 * Eliminates weak inequalities if they are not substantiated.
162 *
163 * @param body
164 */
165 void eliminateWeakInequalities(PBody body) {
166 for (Inequality inequality : body.getConstraintsOfType(Inequality.class)){
167 if (inequality.isEliminable()){
168 inequality.eliminateWeak();
169 derivativeRemoved(inequality, ConstraintRemovalReason.WEAK_INEQUALITY_SELF_LOOP);
170 }
171 }
172 }
173
174 /**
175 * Eliminates all type constraints that are inferrable from other constraints.
176 */
177 void eliminateInferrableTypes(final PBody body, IQueryMetaContext context) {
178 Set<TypeJudgement> subsumedByRetainedConstraints = new HashSet<TypeJudgement>();
179 LinkedList<ITypeConstraint> allTypeConstraints = new LinkedList<ITypeConstraint>();
180 for (PConstraint pConstraint : body.getConstraints()) {
181 if (pConstraint instanceof ITypeConstraint) {
182 allTypeConstraints.add((ITypeConstraint) pConstraint);
183 } else if (pConstraint instanceof ITypeInfoProviderConstraint) {
184 // non-type constraints are all retained
185 final Set<TypeJudgement> directJudgements = ((ITypeInfoProviderConstraint) pConstraint)
186 .getImpliedJudgements(context);
187 subsumedByRetainedConstraints = TypeHelper.typeClosure(subsumedByRetainedConstraints, directJudgements,
188 context);
189 }
190 }
191 Comparator<ITypeConstraint> eliminationOrder = (o1, o2) -> {
192 IInputKey type1 = o1.getEquivalentJudgement().getInputKey();
193 IInputKey type2 = o2.getEquivalentJudgement().getInputKey();
194
195 int result = context.getSuggestedEliminationOrdering().compare(type1, type2);
196 return (result == 0)
197 ? PConstraint.COMPARE_BY_MONOTONOUS_ID.compare(o1, o2)
198 : result;
199 };
200
201 Collections.sort(allTypeConstraints, eliminationOrder);
202 Queue<ITypeConstraint> potentialConstraints = allTypeConstraints; // rename for better comprehension
203
204 while (!potentialConstraints.isEmpty()) {
205 ITypeConstraint candidate = potentialConstraints.poll();
206
207 boolean isSubsumed = subsumedByRetainedConstraints.contains(candidate.getEquivalentJudgement());
208 if (!isSubsumed) {
209 Set<TypeJudgement> typeClosure = subsumedByRetainedConstraints;
210 for (ITypeConstraint subsuming : potentialConstraints) { // the remaining ones
211 final Set<TypeJudgement> directJudgements = subsuming.getImpliedJudgements(context);
212 typeClosure = TypeHelper.typeClosure(typeClosure, directJudgements, context);
213
214 if (typeClosure.contains(candidate.getEquivalentJudgement())) {
215 isSubsumed = true;
216 break;
217 }
218 }
219 }
220 if (isSubsumed) { // eliminated
221 candidate.delete();
222 derivativeRemoved(candidate, ConstraintRemovalReason.TYPE_SUBSUMED);
223 } else { // retained
224 subsumedByRetainedConstraints = TypeHelper.typeClosure(subsumedByRetainedConstraints,
225 candidate.getImpliedJudgements(context), context);
226 }
227 }
228 }
229
230 /**
231 * Inserts "weakened alternative" constraints suggested by the meta context that aid in coming up with a query plan.
232 */
233 void expandWeakenedAlternativeConstraints(PBody body) {
234 Set<TypeJudgement> allJudgements = new HashSet<TypeJudgement>();
235 Set<TypeJudgement> newJudgementsToAdd = new HashSet<TypeJudgement>();
236 Queue<TypeJudgement> judgementsToProcess = new LinkedList<TypeJudgement>();
237 Map<TypeJudgement, List<PConstraint>> traceability = CollectionsFactory.createMap();
238
239 for (ITypeConstraint typeConstraint : body.getConstraintsOfType(ITypeConstraint.class)) {
240 TypeJudgement equivalentJudgement = typeConstraint.getEquivalentJudgement();
241 judgementsToProcess.add(equivalentJudgement);
242 allJudgements.add(equivalentJudgement);
243 traceability.computeIfAbsent(equivalentJudgement, k-> new ArrayList<>()).add(typeConstraint);
244 }
245
246 while (!judgementsToProcess.isEmpty()) {
247 TypeJudgement judgement = judgementsToProcess.poll();
248 for (TypeJudgement alternativeJudgement : judgement.getWeakenedAlternativeJudgements(context)) {
249 if (allJudgements.add(alternativeJudgement)) {
250 newJudgementsToAdd.add(alternativeJudgement);
251 judgementsToProcess.add(alternativeJudgement);
252 traceability.merge(
253 alternativeJudgement,
254 traceability.getOrDefault(judgement, new ArrayList<>()),
255 (old,further) -> {old.addAll(further); return old;}
256 );
257 }
258 }
259 }
260
261 for (TypeJudgement typeJudgement : newJudgementsToAdd) {
262 PConstraint newConstraint = typeJudgement.createConstraintFor(body);
263 for (PConstraint source : traceability.getOrDefault(typeJudgement, Collections.emptyList())) {
264 addTrace(source, newConstraint);
265 }
266 }
267 }
268
269 private Object getConstraintKey(PConstraint constraint) {
270 if (constraint instanceof ITypeConstraint) {
271 return ((ITypeConstraint) constraint).getEquivalentJudgement();
272 }
273 // Do not check duplication for any other types
274 return constraint;
275 }
276
277 void eliminateDuplicateTypeConstraints(PBody body) {
278 Map<Object, PConstraint> constraints = new HashMap<>();
279 for (PConstraint constraint : body.getConstraints()) {
280 Object key = getConstraintKey(constraint);
281 // Retain first found instance of a constraint
282 if (!constraints.containsKey(key)) {
283 constraints.put(key, constraint);
284 }
285 }
286
287 // Retain collected constraints, remove everything else
288 Iterator<PConstraint> iterator = body.getConstraints().iterator();
289 Collection<PConstraint> toRetain = constraints.values();
290 while(iterator.hasNext()){
291 PConstraint next = iterator.next();
292 if (!toRetain.contains(next)){
293 derivativeRemoved(next, ConstraintRemovalReason.DUPLICATE);
294 iterator.remove();
295 }
296 }
297 }
298
299 /**
300 * Verifies the sanity of all constraints. Should be issued as a preventive check before layouting.
301 *
302 * @param body
303 * @throws RetePatternBuildException
304 */
305 void checkSanity(PBody body) {
306 for (PConstraint pConstraint : body.getConstraints())
307 pConstraint.checkSanity();
308 }
309
310}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java
new file mode 100644
index 00000000..c844ccf7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
12import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
13
14/**
15 * An abstract base class for creating alternative representations for PDisjunctions.
16 * @author Zoltan Ujhelyi
17 *
18 */
19public abstract class PDisjunctionRewriter extends AbstractRewriterTraceSource{
20
21 public abstract PDisjunction rewrite(PDisjunction disjunction);
22
23 public PDisjunction rewrite(PQuery query) {
24 return rewrite(query.getDisjunctBodies());
25 }
26
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java
new file mode 100644
index 00000000..eb5422ca
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.List;
15import java.util.WeakHashMap;
16
17import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
18
19/**
20 * A rewriter that stores the previously computed results of a rewriter or a rewriter chain.
21 *
22 * @author Zoltan Ujhelyi
23 * @since 1.0
24 */
25public class PDisjunctionRewriterCacher extends PDisjunctionRewriter {
26
27 private final List<PDisjunctionRewriter> rewriterChain;
28 private WeakHashMap<PDisjunction, PDisjunction> cachedResults =
29 new WeakHashMap<PDisjunction, PDisjunction>();
30
31 private void setupTraceCollectorInChain(){
32 IRewriterTraceCollector collector = getTraceCollector();
33 for(PDisjunctionRewriter rewriter: rewriterChain){
34 rewriter.setTraceCollector(collector);
35 }
36 }
37
38 public PDisjunctionRewriterCacher(PDisjunctionRewriter rewriter) {
39 rewriterChain = Collections.singletonList(rewriter);
40 }
41
42 public PDisjunctionRewriterCacher(PDisjunctionRewriter... rewriters) {
43 rewriterChain = new ArrayList<>(Arrays.asList(rewriters));
44 }
45
46 public PDisjunctionRewriterCacher(List<PDisjunctionRewriter> rewriterChain) {
47 this.rewriterChain = new ArrayList<>(rewriterChain);
48 }
49
50 @Override
51 public PDisjunction rewrite(PDisjunction disjunction) {
52 if (!cachedResults.containsKey(disjunction)) {
53 PDisjunction rewritten = disjunction;
54 setupTraceCollectorInChain();
55 for (PDisjunctionRewriter rewriter : rewriterChain) {
56 rewritten = rewriter.rewrite(rewritten);
57 }
58
59 cachedResults.put(disjunction, rewritten);
60 }
61 return cachedResults.get(disjunction);
62 }
63
64}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java
new file mode 100644
index 00000000..76311d8f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java
@@ -0,0 +1,253 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.ArrayDeque;
12import java.util.ArrayList;
13import java.util.Collections;
14import java.util.Deque;
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.LinkedHashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import tools.refinery.viatra.runtime.matchers.psystem.PBody;
24import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
25import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
26import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
27import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
28import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
29import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.AllowAllFilter;
30import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.ExportedParameterFilter;
31import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.HierarchicalName;
32import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.SameName;
33import tools.refinery.viatra.runtime.matchers.util.Preconditions;
34import tools.refinery.viatra.runtime.matchers.util.Sets;
35
36/**
37 * This rewriter class holds the query flattening logic
38 *
39 * @author Marton Bur
40 *
41 */
42public class PQueryFlattener extends PDisjunctionRewriter {
43
44 /**
45 * Utility function to produce the permutation of every possible mapping of values.
46 *
47 * @param values
48 * @return
49 */
50 private static <K, V> Set<Map<K, V>> permutation(Map<K, Set<V>> values) {
51 // An ordering of keys is defined here which will help restoring the appropriate values after the execution of
52 // the cartesian product
53 List<K> keyList = new ArrayList<>(values.keySet());
54
55 // Produce list of value sets with the ordering defined by keyList
56 List<Set<V>> valuesList = new ArrayList<Set<V>>(keyList.size());
57 for (K key : keyList) {
58 valuesList.add(values.get(key));
59 }
60
61 // Cartesian product will obey ordering of the list
62 Set<List<V>> valueMappings = Sets.cartesianProduct(valuesList);
63
64 // Build result
65 Set<Map<K, V>> result = new LinkedHashSet<>();
66 for (List<V> valueList : valueMappings) {
67 Map<K, V> map = new HashMap<>();
68 for (int i = 0; i < keyList.size(); i++) {
69 map.put(keyList.get(i), valueList.get(i));
70 }
71 result.add(map);
72 }
73
74 return result;
75 }
76
77 private IFlattenCallPredicate flattenCallPredicate;
78
79 public PQueryFlattener(IFlattenCallPredicate flattenCallPredicate) {
80 this.flattenCallPredicate = flattenCallPredicate;
81 }
82
83 @Override
84 public PDisjunction rewrite(PDisjunction disjunction) {
85 PQuery query = disjunction.getQuery();
86
87 // Check for recursion
88 Set<PQuery> allReferredQueries = disjunction.getAllReferredQueries();
89 for (PQuery referredQuery : allReferredQueries) {
90 if (referredQuery.getAllReferredQueries().contains(referredQuery)) {
91 throw new RewriterException("Recursive queries are not supported, can't flatten query named \"{1}\"",
92 new String[] { query.getFullyQualifiedName() }, "Unsupported recursive query", query);
93 }
94 }
95
96 return this.doFlatten(disjunction);
97 }
98
99 /**
100 * Return the list of dependencies (including the root) in chronological order
101 *
102 * @param rootDisjunction
103 * @return
104 */
105 private List<PDisjunction> disjunctionDependencies(PDisjunction rootDisjunction) {
106 // Disjunctions are first collected into a list usign a depth-first approach,
107 // which can be iterated backwards while removing duplicates
108 Deque<PDisjunction> stack = new ArrayDeque<>();
109 LinkedList<PDisjunction> list = new LinkedList<>();
110 stack.push(rootDisjunction);
111 list.add(rootDisjunction);
112
113 while (!stack.isEmpty()) {
114 PDisjunction disjunction = stack.pop();
115 // Collect dependencies
116 for (PBody pBody : disjunction.getBodies()) {
117 for (PConstraint constraint : pBody.getConstraints()) {
118 if (constraint instanceof PositivePatternCall) {
119 PositivePatternCall positivePatternCall = (PositivePatternCall) constraint;
120 if (flattenCallPredicate.shouldFlatten(positivePatternCall)) {
121 // If the above preconditions meet, the call should be flattened
122 PDisjunction calledDisjunction = positivePatternCall.getReferredQuery().getDisjunctBodies();
123 stack.push(calledDisjunction);
124 list.add(calledDisjunction);
125 }
126 }
127 }
128 }
129 }
130
131 // Remove duplicates (keeping the last instance) and reverse order
132 Set<PDisjunction> visited = new HashSet<PDisjunction>();
133 List<PDisjunction> result = new ArrayList<PDisjunction>(list.size());
134
135 list.descendingIterator().forEachRemaining(item -> {
136 if (!visited.contains(item)) {
137 result.add(item);
138 visited.add(item);
139 }
140
141 });
142
143 return result;
144 }
145
146 /**
147 * This function holds the actual flattening logic for a PQuery
148 *
149 * @param rootDisjunction
150 * to be flattened
151 * @return the flattened bodies of the pQuery
152 */
153 private PDisjunction doFlatten(PDisjunction rootDisjunction) {
154
155 Map<PDisjunction, Set<PBody>> flatBodyMapping = new HashMap<>();
156
157 List<PDisjunction> dependencies = disjunctionDependencies(rootDisjunction);
158
159 for (PDisjunction disjunction : dependencies) {
160 Set<PBody> flatBodies = new LinkedHashSet<>();
161 for (PBody body : disjunction.getBodies()) {
162 if (isFlatteningNeeded(body)) {
163 Map<PositivePatternCall, Set<PBody>> flattenedBodies = new HashMap<>();
164 for (PConstraint pConstraint : body.getConstraints()) {
165
166 if (pConstraint instanceof PositivePatternCall) {
167 PositivePatternCall positivePatternCall = (PositivePatternCall) pConstraint;
168 if (flattenCallPredicate.shouldFlatten(positivePatternCall)) {
169 // If the above preconditions meet, do the flattening and return the disjoint bodies
170 PDisjunction calledDisjunction = positivePatternCall.getReferredQuery()
171 .getDisjunctBodies();
172
173 Set<PBody> flattenedBodySet = flatBodyMapping.get(calledDisjunction);
174 Preconditions.checkArgument(!flattenedBodySet.isEmpty());
175 flattenedBodies.put(positivePatternCall, flattenedBodySet);
176 }
177 }
178 }
179 flatBodies.addAll(createSetOfFlatPBodies(body, flattenedBodies));
180 } else {
181 flatBodies.add(prepareFlatPBody(body));
182 }
183 }
184 flatBodyMapping.put(disjunction, flatBodies);
185 }
186
187 return new PDisjunction(rootDisjunction.getQuery(), flatBodyMapping.get(rootDisjunction));
188 }
189
190 /**
191 * Creates the flattened bodies based on the caller body and the called (and already flattened) disjunctions
192 *
193 * @param pBody
194 * the body to flatten
195 * @param flattenedDisjunctions
196 * the
197 * @param flattenedCalls
198 * @return
199 */
200 private Set<PBody> createSetOfFlatPBodies(PBody pBody, Map<PositivePatternCall, Set<PBody>> flattenedCalls) {
201 PQuery pQuery = pBody.getPattern();
202
203 Set<Map<PositivePatternCall, PBody>> conjunctedCalls = permutation(flattenedCalls);
204
205 // The result set containing the merged conjuncted bodies
206 Set<PBody> conjunctedBodies = new HashSet<>();
207
208 for (Map<PositivePatternCall, PBody> calledBodies : conjunctedCalls) {
209 FlattenerCopier copier = createBodyCopier(pQuery, calledBodies);
210
211 int i = 0;
212 HierarchicalName hierarchicalNamingTool = new HierarchicalName();
213 for (PositivePatternCall patternCall : calledBodies.keySet()) {
214 // Merge each called body
215 hierarchicalNamingTool.setCallCount(i++);
216 copier.mergeBody(patternCall, hierarchicalNamingTool, new ExportedParameterFilter());
217 }
218
219 // Merge the caller's constraints to the conjunct body
220 copier.mergeBody(pBody);
221
222 PBody copiedBody = copier.getCopiedBody();
223 copiedBody.setStatus(PQueryStatus.OK);
224 conjunctedBodies.add(copiedBody);
225 }
226
227 return conjunctedBodies;
228 }
229
230 private FlattenerCopier createBodyCopier(PQuery query, Map<PositivePatternCall, PBody> calledBodies) {
231 FlattenerCopier flattenerCopier = new FlattenerCopier(query, calledBodies);
232 flattenerCopier.setTraceCollector(getTraceCollector());
233 return flattenerCopier;
234 }
235
236 private PBody prepareFlatPBody(PBody pBody) {
237 PBodyCopier copier = createBodyCopier(pBody.getPattern(), Collections.<PositivePatternCall, PBody> emptyMap());
238 copier.mergeBody(pBody, new SameName(), new AllowAllFilter());
239 // the copying of the body here is necessary for only one containing PDisjunction can be assigned to a PBody
240 return copier.getCopiedBody();
241 }
242
243 private boolean isFlatteningNeeded(PBody pBody) {
244 // Check if the body contains positive pattern call AND if it should be flattened
245 for (PConstraint pConstraint : pBody.getConstraints()) {
246 if (pConstraint instanceof PositivePatternCall) {
247 return flattenCallPredicate.shouldFlatten((PositivePatternCall) pConstraint);
248 }
249 }
250 return false;
251 }
252
253}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java
new file mode 100644
index 00000000..d0fc286b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException;
12
13/**
14 * An exception to wrap various issues during PDisjunction rewriting.
15 * @author Zoltan Ujhelyi
16 *
17 */
18public class RewriterException extends QueryInitializationException {
19
20 private static final long serialVersionUID = -4703825954995497932L;
21
22 public RewriterException(String message, String[] context, String shortMessage, Object patternDescription,
23 Throwable cause) {
24 super(message, context, shortMessage, patternDescription, cause);
25 }
26
27 public RewriterException(String message, String[] context, String shortMessage, Object patternDescription) {
28 super(message, context, shortMessage, patternDescription);
29 }
30
31}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java
new file mode 100644
index 00000000..71459558
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java
@@ -0,0 +1,63 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.LinkedHashSet;
12import java.util.Set;
13
14import tools.refinery.viatra.runtime.matchers.context.IInputKey;
15import tools.refinery.viatra.runtime.matchers.context.surrogate.SurrogateQueryRegistry;
16import tools.refinery.viatra.runtime.matchers.psystem.PBody;
17import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
18import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
19import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint;
20import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction;
21import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
22import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus;
23import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
24import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
25
26/**
27 * @author Zoltan Ujhelyi
28 *
29 */
30public class SurrogateQueryRewriter extends PDisjunctionRewriter {
31
32 @Override
33 public PDisjunction rewrite(PDisjunction disjunction) {
34 Set<PBody> replacedBodies = new LinkedHashSet<>();
35 for (PBody body : disjunction.getBodies()) {
36 PBodyCopier copier = new PBodyCopier(body, getTraceCollector()) {
37
38 @Override
39 protected void copyTypeConstraint(TypeConstraint typeConstraint) {
40 PVariable[] mappedVariables = extractMappedVariables(typeConstraint);
41 Tuple variablesTuple = Tuples.flatTupleOf((Object[])mappedVariables);
42 final IInputKey supplierKey = typeConstraint.getSupplierKey();
43 if(SurrogateQueryRegistry.instance().hasSurrogateQueryFQN(supplierKey)) {
44 PQuery surrogateQuery = SurrogateQueryRegistry.instance().getSurrogateQuery(supplierKey);
45 if (surrogateQuery == null) {
46 throw new IllegalStateException(
47 String.format("Surrogate query for feature %s not found",
48 supplierKey.getPrettyPrintableName()));
49 }
50 addTrace(typeConstraint, new PositivePatternCall(getCopiedBody(), variablesTuple, surrogateQuery));
51 } else {
52 addTrace(typeConstraint, new TypeConstraint(getCopiedBody(), variablesTuple, supplierKey));
53 }
54 }
55 };
56 PBody modifiedBody = copier.getCopiedBody();
57 replacedBodies.add(modifiedBody);
58 modifiedBody.setStatus(PQueryStatus.OK);
59 }
60 return new PDisjunction(replacedBodies);
61 }
62
63}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java
new file mode 100644
index 00000000..10337979
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.psystem.rewriters;
10
11import java.util.HashMap;
12import java.util.LinkedHashMap;
13import java.util.Map;
14
15import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator;
16import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
17import tools.refinery.viatra.runtime.matchers.psystem.PVariable;
18import tools.refinery.viatra.runtime.matchers.util.Preconditions;
19
20/**
21 * A wrapper for {@link IExpressionEvaluator} which is capable of correctly mapping variable names used by the
22 * expression.
23 *
24 * @author Grill Balázs
25 *
26 */
27class VariableMappingExpressionEvaluatorWrapper implements IExpressionEvaluator {
28
29 private final IExpressionEvaluator wrapped;
30 private final Map<String, String> variableMapping;
31
32 public VariableMappingExpressionEvaluatorWrapper(IExpressionEvaluator wrapped,
33 Map<PVariable, PVariable> variableMapping) {
34
35 // Support to rewrap an already wrapped expression.
36 boolean rewrap = wrapped instanceof VariableMappingExpressionEvaluatorWrapper;
37 this.wrapped = rewrap ? ((VariableMappingExpressionEvaluatorWrapper)wrapped).wrapped : wrapped;
38
39 // Instead of just saving the reference of the mapping, save the actual (trimmed) state of the mapping as it
40 // may change during copying (especially during flattening). A LinkedHashMap is used to retain ordering of
41 // original parameter names iterator.
42 this.variableMapping = new LinkedHashMap<>();
43
44 // Index map by variable names
45 Map<String, PVariable> names = new HashMap<>();
46 for (PVariable originalVar : variableMapping.keySet()) {
47 names.put(originalVar.getName(), originalVar);
48 }
49
50 // In case of rewrapping, current names are contained by the previous mapping
51 Map<String, String> previousMapping = null;
52 if (rewrap){
53 previousMapping = ((VariableMappingExpressionEvaluatorWrapper)wrapped).variableMapping;
54 }
55
56 // Populate mapping
57 for (String inputParameterName : this.wrapped.getInputParameterNames()) {
58 String parameterName = rewrap ? previousMapping.get(inputParameterName) : inputParameterName;
59 Preconditions.checkArgument(parameterName != null);
60 PVariable original = names.get(parameterName);
61 Preconditions.checkArgument(original != null);
62 PVariable mapped = variableMapping.get(original);
63 if (mapped != null){
64 this.variableMapping.put(inputParameterName, mapped.getName());
65 }
66 }
67 }
68
69 @Override
70 public String getShortDescription() {
71 return wrapped.getShortDescription();
72 }
73
74 @Override
75 public Iterable<String> getInputParameterNames() {
76 return variableMapping.values();
77 }
78
79 @Override
80 public Object evaluateExpression(final IValueProvider provider) throws Exception {
81 return wrapped.evaluateExpression(variableName -> {
82 String mappedVariableName = variableMapping.get(variableName);
83 Preconditions.checkArgument(mappedVariableName != null, "Could not find variable %s", variableName);
84 return provider.getValue(mappedVariableName);
85 });
86 }
87
88}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java
new file mode 100644
index 00000000..a26d9193
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Map;
16import java.util.Objects;
17import java.util.Set;
18
19/**
20 * Common implementation methods for immutable and volatile tuples. The class should not be used directly in client
21 * code, except for the definition of new tuple implementations.
22 *
23 * @author Zoltan Ujhelyi
24 * @since 1.7
25 */
26public abstract class AbstractTuple implements ITuple {
27
28 /**
29 * As the tuple is supposed to be immutable, do not modify the returned array.
30 *
31 * @return the array containing all elements of this Tuple
32 */
33 @Override
34 public Object[] getElements() {
35 Object[] allElements = new Object[getSize()];
36 for (int i = 0; i < allElements.length; ++i)
37 allElements[i] = get(i);
38 return allElements;
39 }
40
41 /**
42 * @return the set containing all distinct elements of this Tuple, cast as type T
43 */
44 @Override
45 @SuppressWarnings("unchecked")
46 public <T> Set<T> getDistinctElements() {
47 Set<T> result = new HashSet<T>();
48 Object[] elements = getElements();
49 for (Object object : elements) {
50 result.add((T) object);
51 }
52 return result;
53 }
54
55 /**
56 * Calculates an inverted index of the elements of this pattern. For each element, the index of the (last)
57 * occurrence is calculated.
58 *
59 * @return the inverted index mapping each element of this pattern to its index in the array
60 */
61 @Override
62 public Map<Object, Integer> invertIndex() {
63 Map<Object, Integer> result = new HashMap<Object, Integer>();
64 for (int i = 0; i < getSize(); i++)
65 result.put(get(i), i);
66 return result;
67 }
68
69 /**
70 * Calculates an inverted index of the elements of this pattern. For each element, the index of all of its
71 * occurrences is calculated.
72 *
73 * @return the inverted index mapping each element of this pattern to its index in the array
74 */
75 @Override
76 public Map<Object, List<Integer>> invertIndexWithMupliplicity() {
77 Map<Object, List<Integer>> result = new HashMap<Object, List<Integer>>();
78 for (int i = 0; i < getSize(); i++) {
79 Object value = get(i);
80 List<Integer> indices = result.computeIfAbsent(value, v -> new ArrayList<>());
81 indices.add(i);
82 }
83 return result;
84 }
85
86 /**
87 * @since 1.7
88 */
89 protected IndexOutOfBoundsException raiseIndexingError(int index) {
90 return new IndexOutOfBoundsException(
91 String.format("No value at position %d for %s instance %s", index, getClass().getSimpleName(), this));
92 }
93
94 /**
95 * Compares the elements stored in this tuple to another tuple
96 */
97 protected boolean internalEquals(ITuple other) {
98 if (getSize() != other.getSize())
99 return false;
100 boolean result = true;
101 for (int i = 0; result && i < getSize(); ++i) {
102 Object ours = get(i);
103 Object theirs = other.get(i);
104 result = result && Objects.equals(ours, theirs);
105 }
106 return result;
107 }
108
109 @Override
110 public String toString() {
111 StringBuilder s = new StringBuilder();
112 s.append("T(");
113 for (Object o : getElements()) {
114 s.append(o == null ? "null" : o.toString());
115 s.append(';');
116 }
117 s.append(')');
118 return s.toString();
119 }
120
121 /**
122 * @since 1.7
123 */
124 protected int doCalcHash() {
125 final int PRIME = 31;
126 int hash = 1;
127 for (int i = 0; i < getSize(); i++) {
128 hash = PRIME * hash;
129 Object element = get(i);
130 if (element != null)
131 hash += element.hashCode();
132 }
133 return hash;
134 }
135
136} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java
new file mode 100644
index 00000000..6f46b763
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11/**
12 * Base class for all flat tuple implementations.
13 * Flat tuples store all elements locally (do not reference other tuples).
14 *
15 * @author Gabor Bergmann
16 * @since 1.7
17 */
18public abstract class BaseFlatTuple extends Tuple {
19
20}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java
new file mode 100644
index 00000000..03f9ea89
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11/**
12 * Common functionality of left inheritance tuple implementations.
13 *
14 * <p>Left inheritance tuples inherit their first few elements from another tuple,
15 * and extend it with additional "local" elements.
16 *
17 * @author Gabor Bergmann
18 * @since 1.7
19 */
20public abstract class BaseLeftInheritanceTuple extends Tuple {
21
22 /**
23 * The number of elements that aren't stored locally, but inherited from an ancestor Tuple instead.
24 */
25 protected final int inheritedIndex;
26 /**
27 * This object contains the same elements as the ancestor on the first inheritedIndex positions
28 */
29 protected final Tuple ancestor;
30
31 /**
32 * @param ancestor
33 */
34 public BaseLeftInheritanceTuple(Tuple ancestor) {
35 super();
36 this.ancestor = ancestor;
37 this.inheritedIndex = ancestor.getSize();
38 }
39
40 /**
41 * @return the number of local (non-inherited) elements
42 */
43 public abstract int getLocalSize();
44
45 /**
46 * Optimized equals calculation (prediction: true, since hash values match)
47 */
48 @Override
49 protected boolean internalEquals(ITuple other) {
50 if (other instanceof BaseLeftInheritanceTuple) {
51 BaseLeftInheritanceTuple blit = (BaseLeftInheritanceTuple) other;
52 if (blit.inheritedIndex == this.inheritedIndex) {
53 if (this.ancestor.equals(blit.ancestor)) {
54 return localEquals(blit);
55 } else return false;
56 }
57 }
58 return super.internalEquals(other);
59 }
60
61 /**
62 * Checks the equivalence of local elements only, after ancestor tuple has been determined to be equal.
63 */
64 protected abstract boolean localEquals(BaseLeftInheritanceTuple other);
65}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java
new file mode 100644
index 00000000..8bbb0ac2
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12import java.util.Arrays;
13
14/**
15 * Default Tuple implementation, with statically unknown arity.
16 * @author Gabor Bergmann
17 */
18public final class FlatTuple extends BaseFlatTuple {
19
20 /**
21 * Array of substituted values. DO NOT MODIFY! Use Constructor to build a new instance instead.
22 */
23 private final Object[] elements;
24
25 /**
26 * Creates a FlatTuple instance, fills it with the given array.
27 * <p> Users should consider calling {@link Tuples#flatTupleOf(Object...)} instead to save memory on low-arity tuples.
28 *
29 * @param elements
30 * array of substitution values
31 */
32 protected FlatTuple(Object... elements) {
33 this.elements = Arrays.copyOf(elements, elements.length);
34 calcHash();
35 }
36
37 @Override
38 public Object get(int index) {
39 return elements[index];
40 }
41
42 @Override
43 public int getSize() {
44 return elements.length;
45 }
46
47 @Override
48 public Object[] getElements() {
49 return elements;
50 }
51
52 @Override
53 protected boolean internalEquals(ITuple other) {
54 if (other instanceof FlatTuple) {
55 return Arrays.equals(elements, ((FlatTuple) other).elements);
56 } else
57 return super.internalEquals(other);
58 }
59
60}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java
new file mode 100644
index 00000000..93f412b7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11/**
12 * Flat tuple with statically known arity of 0.
13 *
14 * @author Gabor Bergmann
15 * @since 1.7
16 *
17 */
18public final class FlatTuple0 extends BaseFlatTuple {
19 protected static final FlatTuple0 INSTANCE = new FlatTuple0();
20
21 private FlatTuple0() {
22 calcHash();
23 }
24
25 @Override
26 public int getSize() {
27 return 0;
28 }
29
30 @Override
31 public Object get(int index) {
32 throw raiseIndexingError(index);
33 }
34
35 private static final Object[] NULLARY_ARRAY = new Object[0];
36
37 @Override
38 public Object[] getElements() {
39 return NULLARY_ARRAY;
40 }
41
42 @Override
43 protected boolean internalEquals(ITuple other) {
44 return 0 == other.getSize();
45 }
46}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java
new file mode 100644
index 00000000..b3b1c312
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * Flat tuple with statically known arity of 1.
15 *
16 * @author Gabor Bergmann
17 * @since 1.7
18 *
19 */
20public final class FlatTuple1 extends BaseFlatTuple {
21 private final Object element0;
22
23 protected FlatTuple1(Object element0) {
24 this.element0 = element0;
25 calcHash();
26 }
27
28 @Override
29 public int getSize() {
30 return 1;
31 }
32
33 @Override
34 public Object get(int index) {
35 if (index == 0) return element0;
36 else throw raiseIndexingError(index);
37 }
38
39 @Override
40 protected boolean internalEquals(ITuple other) {
41 return 1 == other.getSize() &&
42 Objects.equals(element0, other.get(0));
43 }
44}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java
new file mode 100644
index 00000000..2dcfd718
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * Flat tuple with statically known arity of 2.
15 *
16 * @author Gabor Bergmann
17 * @since 1.7
18 *
19 */
20public final class FlatTuple2 extends BaseFlatTuple {
21 private final Object element0;
22 private final Object element1;
23
24 protected FlatTuple2(Object element0, Object element1) {
25 this.element0 = element0;
26 this.element1 = element1;
27 calcHash();
28 }
29
30 @Override
31 public int getSize() {
32 return 2;
33 }
34
35 @Override
36 public Object get(int index) {
37 switch(index) {
38 case 0 : return element0;
39 case 1 : return element1;
40 default: throw raiseIndexingError(index);
41 }
42 }
43
44 @Override
45 protected boolean internalEquals(ITuple other) {
46 return 2 == other.getSize() &&
47 Objects.equals(element0, other.get(0)) &&
48 Objects.equals(element1, other.get(1));
49 }
50
51}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java
new file mode 100644
index 00000000..50cee57e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * Flat tuple with statically known arity of 3.
15 *
16 * @author Gabor Bergmann
17 * @since 1.7
18 *
19 */
20public final class FlatTuple3 extends BaseFlatTuple {
21 private final Object element0;
22 private final Object element1;
23 private final Object element2;
24
25 protected FlatTuple3(Object element0, Object element1, Object element2) {
26 this.element0 = element0;
27 this.element1 = element1;
28 this.element2 = element2;
29 calcHash();
30 }
31
32 @Override
33 public int getSize() {
34 return 3;
35 }
36
37 @Override
38 public Object get(int index) {
39 switch (index) {
40 case 0: return element0;
41 case 1: return element1;
42 case 2: return element2;
43 default: throw raiseIndexingError(index);
44 }
45 }
46
47 @Override
48 protected boolean internalEquals(ITuple other) {
49 return 3 == other.getSize() &&
50 Objects.equals(element0, other.get(0)) &&
51 Objects.equals(element1, other.get(1)) &&
52 Objects.equals(element2, other.get(2));
53 }
54
55}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java
new file mode 100644
index 00000000..024c94f4
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java
@@ -0,0 +1,59 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * Flat tuple with statically known arity of 4.
15 *
16 * @author Gabor Bergmann
17 * @since 1.7
18 *
19 */
20public final class FlatTuple4 extends BaseFlatTuple {
21 private final Object element0;
22 private final Object element1;
23 private final Object element2;
24 private final Object element3;
25
26 protected FlatTuple4(Object element0, Object element1, Object element2, Object element3) {
27 this.element0 = element0;
28 this.element1 = element1;
29 this.element2 = element2;
30 this.element3 = element3;
31 calcHash();
32 }
33
34 @Override
35 public int getSize() {
36 return 4;
37 }
38
39 @Override
40 public Object get(int index) {
41 switch(index) {
42 case 0: return element0;
43 case 1: return element1;
44 case 2: return element2;
45 case 3: return element3;
46 default: throw raiseIndexingError(index);
47 }
48 }
49
50 @Override
51 protected boolean internalEquals(ITuple other) {
52 return 4 == other.getSize() &&
53 Objects.equals(element0, other.get(0)) &&
54 Objects.equals(element1, other.get(1)) &&
55 Objects.equals(element2, other.get(2)) &&
56 Objects.equals(element3, other.get(3));
57 }
58
59}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java
new file mode 100644
index 00000000..f5dab2a5
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java
@@ -0,0 +1,27 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11/**
12 * A tuple that allows modifying the underlying value. Should not be used for non-volatile tuples.
13 *
14 * @author Zoltan Ujhelyi
15 * @since 1.7
16 */
17public interface IModifiableTuple extends ITuple {
18
19 /**
20 * Sets the selected value for a tuple
21 *
22 * @pre: 0 <= index < getSize()
23 *
24 */
25 void set(int index, Object value);
26
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java
new file mode 100644
index 00000000..92014781
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java
@@ -0,0 +1,64 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.List;
12import java.util.Map;
13import java.util.Set;
14
15/**
16 * Represents both mutable and immutable tuples
17 *
18 * @author Zoltan Ujhelyi
19 * @since 1.7
20 *
21 */
22public interface ITuple {
23
24 /**
25 * @pre: 0 <= index < getSize()
26 *
27 * @return the element at the specified index
28 */
29 Object get(int index);
30
31 /**
32 * As the tuple is supposed to be immutable, do not modify the returned array.
33 * @return the array containing all elements of this Tuple
34 */
35 Object[] getElements();
36
37 /**
38 * @return the set containing all distinct elements of this Tuple, cast as type T
39 */
40 <T> Set<T> getDistinctElements();
41
42 /**
43 * @return number of elements
44 */
45 int getSize();
46
47 /**
48 * Calculates an inverted index of the elements of this pattern. For each element, the index of the (last)
49 * occurrence is calculated.
50 *
51 * @return the inverted index mapping each element of this pattern to its index in the array
52 */
53 Map<Object, Integer> invertIndex();
54
55 /**
56 * Calculates an inverted index of the elements of this pattern. For each element, the index of all of its
57 * occurrences is calculated.
58 *
59 * @return the inverted index mapping each element of this pattern to its index in the array
60 */
61 Map<Object, List<Integer>> invertIndexWithMupliplicity();
62
63 Tuple toImmutable();
64} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java
new file mode 100644
index 00000000..dcdfc376
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java
@@ -0,0 +1,172 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12import java.util.Arrays;
13import java.util.Objects;
14
15/**
16 *
17 * Tuple that inherits another tuple on the left.
18 *
19 * @author Gabor Bergmann
20 *
21 */
22public final class LeftInheritanceTuple extends BaseLeftInheritanceTuple {
23 /**
24 * Array of substituted values above inheritedIndex. DO NOT MODIFY! Use Constructor to build a new instance instead.
25 */
26 private final Object[] localElements;
27
28 //
29 // /**
30 // * Creates a Tuple instance, fills it with the given array.
31 // * @pre: no elements are null
32 // * @param elements array of substitution values
33 // */
34 // public Tuple(Object[] elements)
35 // {
36 // this.localElements = elements;
37 // this.ancestor=null;
38 // this.inheritedIndex = 0;
39 // calcHash();
40 // }
41
42
43
44 /**
45 * Creates a Tuple instance, lets it inherit from an ancestor, extends it with a given array. @pre: no elements are
46 * null
47 *
48 * @param localElements
49 * array of substitution values
50 */
51 LeftInheritanceTuple(Tuple ancestor, Object[] localElements) {
52 super(ancestor);
53 this.localElements = localElements;
54 calcHash();
55 }
56
57 //
58 // /**
59 // * Creates a Tuple instance of size one, fills it with the given object.
60 // * @pre: o!=null
61 // * @param o the single substitution
62 // */
63 // public Tuple(Object o)
64 // {
65 // localElements = new Object [1];
66 // localElements[0] = o;
67 // this.ancestor=null;
68 // this.inheritedIndex = 0;
69 // calcHash();
70 // }
71 //
72 // /**
73 // * Creates a Tuple instance of size two, fills it with the given objects.
74 // * @pre: o1!=null, o2!=null
75 // */
76 // public Tuple(Object o1, Object o2)
77 // {
78 // localElements = new Object [2];
79 // localElements[0] = o1;
80 // localElements[1] = o2;
81 // this.ancestor=null;
82 // this.inheritedIndex = 0;
83 // calcHash();
84 // }
85 //
86 // /**
87 // * Creates a Tuple instance of size three, fills it with the given
88 // objects.
89 // * @pre: o1!=null, o2!=null, o3!=null
90 // */
91 // public Tuple(Object o1, Object o2, Object o3)
92 // {
93 // localElements = new Object [3];
94 // localElements[0] = o1;
95 // localElements[1] = o2;
96 // localElements[2] = o3;
97 // this.ancestor=null;
98 // this.inheritedIndex = 0;
99 // calcHash();
100 // }
101
102 /**
103 * @return number of elements
104 */
105 public int getSize() {
106 return inheritedIndex + localElements.length;
107 }
108
109 @Override
110 public int getLocalSize() {
111 return localElements.length;
112 }
113
114 /**
115 * @pre: 0 <= index < getSize()
116 *
117 * @return the element at the specified index
118 */
119 public Object get(int index) {
120 return (index < inheritedIndex) ? ancestor.get(index) : localElements[index - inheritedIndex];
121 }
122
123 /**
124 * Optimized hash calculation
125 */
126 @Override
127 void calcHash() {
128 final int PRIME = 31;
129 cachedHash = ancestor.hashCode();
130 for (int i = 0; i < localElements.length; i++) {
131 cachedHash = PRIME * cachedHash;
132 Object element = localElements[i];
133 if (element != null)
134 cachedHash += element.hashCode();
135 }
136 }
137
138 /**
139 * Optimized equals calculation (prediction: true, since hash values match)
140 */
141 @Override
142 protected boolean localEquals(BaseLeftInheritanceTuple other) {
143 if (other instanceof LeftInheritanceTuple) {
144 LeftInheritanceTuple lit = (LeftInheritanceTuple)other;
145 return Arrays.equals(this.localElements, lit.localElements);
146 } else {
147 if (localElements.length != other.getLocalSize())
148 return false;
149 int index = inheritedIndex;
150 for (int i = 0; i<localElements.length; ++i) {
151 if (! Objects.equals(localElements[i], other.get(index++)))
152 return false;
153 }
154 return true;
155 }
156 }
157
158 // public int compareTo(Object arg0) {
159 // Tuple other = (Tuple) arg0;
160 //
161 // int retVal = cachedHash - other.cachedHash;
162 // if (retVal==0) retVal = elements.length - other.elements.length;
163 // for (int i=0; retVal==0 && i<elements.length; ++i)
164 // {
165 // if (elements[i] == null && other.elements[i] != null) retVal = -1;
166 // else if (other.elements[i] == null) retVal = 1;
167 // else retVal = elements[i].compareTo(other.elements[i]);
168 // }
169 // return retVal;
170 // }
171
172}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple1.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple1.java
new file mode 100644
index 00000000..61123176
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple1.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public final class LeftInheritanceTuple1 extends BaseLeftInheritanceTuple {
18 /**
19 * A single substituted value after inheritedIndex.
20 */
21 private final Object localElement;
22
23 /**
24 * @param ancestor
25 * @param localElement
26 */
27 protected LeftInheritanceTuple1(Tuple ancestor, Object localElement) {
28 super(ancestor);
29 this.localElement = localElement;
30 calcHash();
31 }
32
33 /**
34 * @return number of elements
35 */
36 public int getSize() {
37 return inheritedIndex + 1;
38 }
39
40 @Override
41 public int getLocalSize() {
42 return 1;
43 }
44
45 /**
46 * @pre: 0 <= index < getSize()
47 *
48 * @return the element at the specified index
49 */
50 public Object get(int index) {
51 int local = index - inheritedIndex;
52 if (local < 0)
53 return ancestor.get(index);
54 else if (local == 0) return localElement;
55 else throw raiseIndexingError(index);
56 }
57
58 /**
59 * Optimized hash calculation
60 */
61 @Override
62 void calcHash() {
63 final int PRIME = 31;
64 cachedHash = ancestor.hashCode();
65 cachedHash = PRIME * cachedHash;
66 if (localElement != null) cachedHash += localElement.hashCode();
67 }
68
69 /**
70 * Optimized equals calculation (prediction: true, since hash values match)
71 */
72 @Override
73 protected boolean localEquals(BaseLeftInheritanceTuple other) {
74 if (other instanceof LeftInheritanceTuple1) {
75 LeftInheritanceTuple1 lit = (LeftInheritanceTuple1)other;
76 return Objects.equals(this.localElement, lit.localElement);
77 } else {
78 return (1 == other.getLocalSize()) &&
79 Objects.equals(localElement, other.get(inheritedIndex));
80 }
81 }
82
83}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple2.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple2.java
new file mode 100644
index 00000000..e9fb98e8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple2.java
@@ -0,0 +1,73 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public final class LeftInheritanceTuple2 extends BaseLeftInheritanceTuple {
18 private final Object localElement0;
19 private final Object localElement1;
20
21 protected LeftInheritanceTuple2(Tuple ancestor, Object localElement0, Object localElement1) {
22 super(ancestor);
23 this.localElement0 = localElement0;
24 this.localElement1 = localElement1;
25 calcHash();
26 }
27
28 @Override
29 public int getLocalSize() {
30 return 2;
31 }
32
33 @Override
34 public int getSize() {
35 return inheritedIndex + 2;
36 }
37
38 @Override
39 public Object get(int index) {
40 int local = index - inheritedIndex;
41 if (local < 0)
42 return ancestor.get(index);
43 else if (local == 0) return localElement0;
44 else if (local == 1) return localElement1;
45 else throw raiseIndexingError(index);
46 }
47
48 /**
49 * Optimized hash calculation
50 */
51 @Override
52 void calcHash() {
53 final int PRIME = 31;
54 cachedHash = ancestor.hashCode();
55 cachedHash = PRIME * cachedHash;
56 if (localElement0 != null) cachedHash += localElement0.hashCode();
57 cachedHash = PRIME * cachedHash;
58 if (localElement1 != null) cachedHash += localElement1.hashCode();
59 }
60
61 @Override
62 protected boolean localEquals(BaseLeftInheritanceTuple other) {
63 if (other instanceof LeftInheritanceTuple2) {
64 LeftInheritanceTuple2 lit = (LeftInheritanceTuple2)other;
65 return Objects.equals(this.localElement0, lit.localElement0) &&
66 Objects.equals(this.localElement1, lit.localElement1);
67 } else {
68 return (2 == other.getLocalSize()) &&
69 Objects.equals(localElement0, other.get(inheritedIndex)) &&
70 Objects.equals(localElement1, other.get(inheritedIndex + 1));
71 }
72 }
73}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple3.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple3.java
new file mode 100644
index 00000000..e61d196f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple3.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public final class LeftInheritanceTuple3 extends BaseLeftInheritanceTuple {
18 private final Object localElement0;
19 private final Object localElement1;
20 private final Object localElement2;
21
22 protected LeftInheritanceTuple3(Tuple ancestor, Object localElement0, Object localElement1, Object localElement2) {
23 super(ancestor);
24 this.localElement0 = localElement0;
25 this.localElement1 = localElement1;
26 this.localElement2 = localElement2;
27 calcHash();
28 }
29
30 @Override
31 public int getLocalSize() {
32 return 3;
33 }
34
35 @Override
36 public int getSize() {
37 return inheritedIndex + 3;
38 }
39
40 @Override
41 public Object get(int index) {
42 int local = index - inheritedIndex;
43 if (local < 0)
44 return ancestor.get(index);
45 else if (local == 0) return localElement0;
46 else if (local == 1) return localElement1;
47 else if (local == 2) return localElement2;
48 else throw raiseIndexingError(index);
49 }
50
51 /**
52 * Optimized hash calculation
53 */
54 @Override
55 void calcHash() {
56 final int PRIME = 31;
57 cachedHash = ancestor.hashCode();
58 cachedHash = PRIME * cachedHash;
59 if (localElement0 != null) cachedHash += localElement0.hashCode();
60 cachedHash = PRIME * cachedHash;
61 if (localElement1 != null) cachedHash += localElement1.hashCode();
62 cachedHash = PRIME * cachedHash;
63 if (localElement2 != null) cachedHash += localElement2.hashCode();
64 }
65
66 @Override
67 protected boolean localEquals(BaseLeftInheritanceTuple other) {
68 if (other instanceof LeftInheritanceTuple3) {
69 LeftInheritanceTuple3 lit = (LeftInheritanceTuple3)other;
70 return Objects.equals(this.localElement0, lit.localElement0) &&
71 Objects.equals(this.localElement1, lit.localElement1) &&
72 Objects.equals(this.localElement2, lit.localElement2);
73 } else {
74 return (3 == other.getLocalSize()) &&
75 Objects.equals(localElement0, other.get(inheritedIndex)) &&
76 Objects.equals(localElement1, other.get(inheritedIndex + 1)) &&
77 Objects.equals(localElement2, other.get(inheritedIndex + 2));
78 }
79 }
80
81}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple4.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple4.java
new file mode 100644
index 00000000..4e50f1e1
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple4.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Objects;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public final class LeftInheritanceTuple4 extends BaseLeftInheritanceTuple {
18 private final Object localElement0;
19 private final Object localElement1;
20 private final Object localElement2;
21 private final Object localElement3;
22
23 protected LeftInheritanceTuple4(Tuple ancestor, Object localElement0, Object localElement1, Object localElement2,
24 Object localElement3) {
25 super(ancestor);
26 this.localElement0 = localElement0;
27 this.localElement1 = localElement1;
28 this.localElement2 = localElement2;
29 this.localElement3 = localElement3;
30 calcHash();
31 }
32
33 @Override
34 public int getLocalSize() {
35 return 4;
36 }
37
38 @Override
39 public int getSize() {
40 return inheritedIndex + 4;
41 }
42
43 @Override
44 public Object get(int index) {
45 int local = index - inheritedIndex;
46 if (local < 0)
47 return ancestor.get(index);
48 else if (local == 0) return localElement0;
49 else if (local == 1) return localElement1;
50 else if (local == 2) return localElement2;
51 else if (local == 3) return localElement3;
52 else throw raiseIndexingError(index);
53 }
54
55 /**
56 * Optimized hash calculation
57 */
58 @Override
59 void calcHash() {
60 final int PRIME = 31;
61 cachedHash = ancestor.hashCode();
62 cachedHash = PRIME * cachedHash;
63 if (localElement0 != null) cachedHash += localElement0.hashCode();
64 cachedHash = PRIME * cachedHash;
65 if (localElement1 != null) cachedHash += localElement1.hashCode();
66 cachedHash = PRIME * cachedHash;
67 if (localElement2 != null) cachedHash += localElement2.hashCode();
68 cachedHash = PRIME * cachedHash;
69 if (localElement3 != null) cachedHash += localElement3.hashCode();
70 }
71
72 @Override
73 protected boolean localEquals(BaseLeftInheritanceTuple other) {
74 if (other instanceof LeftInheritanceTuple4) {
75 LeftInheritanceTuple4 lit = (LeftInheritanceTuple4)other;
76 return Objects.equals(this.localElement0, lit.localElement0) &&
77 Objects.equals(this.localElement1, lit.localElement1) &&
78 Objects.equals(this.localElement2, lit.localElement2) &&
79 Objects.equals(this.localElement3, lit.localElement3);
80 } else {
81 return (4 == other.getLocalSize()) &&
82 Objects.equals(localElement0, other.get(inheritedIndex)) &&
83 Objects.equals(localElement1, other.get(inheritedIndex + 1)) &&
84 Objects.equals(localElement2, other.get(inheritedIndex + 2)) &&
85 Objects.equals(localElement3, other.get(inheritedIndex + 3));
86 }
87 }
88}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/MaskedTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/MaskedTuple.java
new file mode 100644
index 00000000..a5d1991c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/MaskedTuple.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2009 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12/**
13 * A tuple that transparently provides a masked (transformed) view of another tuple.
14 *
15 * @author Gabor Bergmann
16 * @since 2.0
17 *
18 */
19public class MaskedTuple extends Tuple {
20
21 Tuple wrapped;
22 TupleMask mask;
23
24 public MaskedTuple(Tuple wrapped, TupleMask mask) {
25 super();
26 // if (wrapped instanceof MaskedTuple) {
27 // MaskedTuple parent = (MaskedTuple)wrapped;
28 // this.wrapped = parent.wrapped;
29 // this.mask = mask.transform(parent.mask);
30 // }
31 // else
32 //{
33 this.wrapped = wrapped;
34 this.mask = mask;
35 //}
36 }
37
38 @Override
39 public Object get(int index) {
40 return wrapped.get(mask.indices[index]);
41 }
42
43 @Override
44 public int getSize() {
45 return mask.indices.length;
46 }
47
48}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuple.java
new file mode 100644
index 00000000..d94f545f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuple.java
@@ -0,0 +1,69 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12/**
13 * Immutable tuple. Obtain instances via utility class {@link Tuples}.
14 * @author Gabor Bergmann
15 *
16 */
17public abstract class Tuple extends AbstractTuple {
18
19 /**
20 * Caches precalculated hash value
21 */
22 protected int cachedHash;
23
24 /**
25 * Creates a Tuple instance Derivatives should call calcHash()
26 */
27 protected Tuple() {
28 // calcHash();
29 }
30
31 /**
32 * Hash calculation. Overrides should keep semantics.
33 */
34 void calcHash() {
35 cachedHash = doCalcHash();
36 }
37
38 @Override
39 public boolean equals(Object obj) {
40 if (this == obj)
41 return true;
42 if (obj instanceof ITuple) {
43 final ITuple other = (ITuple) obj;
44 return cachedHash == other.hashCode() && internalEquals(other);
45 }
46 return false;
47 }
48
49 @Override
50 public int hashCode() {
51 // Calculated by #calcHash
52 return cachedHash;
53 }
54
55 public Tuple replaceAll(Object obsolete, Object replacement) {
56 Object[] oldElements = getElements();
57 Object[] newElements = new Object[oldElements.length];
58 for (int i = 0; i < oldElements.length; ++i) {
59 newElements[i] = obsolete.equals(oldElements[i]) ? replacement : oldElements[i];
60 }
61 return Tuples.flatTupleOf(newElements);
62 }
63
64 @Override
65 public Tuple toImmutable() {
66 return this;
67 }
68
69}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask.java
new file mode 100644
index 00000000..49c55fef
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask.java
@@ -0,0 +1,560 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.OptionalInt;
19import java.util.Set;
20
21/**
22 *
23 * Specifies select indices of a tuple. If viewed through this mask (see {@link #transform(ITuple)}), the signature of the pattern will consist of its
24 * individual substitutions at the given positions, in the exact same order as they appear in indices[].
25 *
26 * @author Gabor Bergmann
27 */
28public class TupleMask {
29 /**
30 * indices[i] specifies the index of the substitution in the original tuple that occupies the i-th place in the
31 * masked signature.
32 */
33 public final int[] indices;
34 /**
35 * the size of the tuple this mask is applied to
36 */
37 public int sourceWidth;
38 /**
39 * indicesSorted is indices, sorted in ascending order.
40 * null by default, call {@link #ensureIndicesSorted()} before using
41 */
42 int[] indicesSorted;
43
44 /**
45 * true if no index occurs twice; computed on demand by {@link #isNonrepeating()}
46 */
47 Boolean isNonrepeating;
48
49 /**
50 * Creates a TupleMask instance with the given indices array
51 * <p> indicesSorted and isNonrepeating may be OPTIONALLY given if known.
52 * @since 2.0
53 */
54 protected TupleMask(int[] indices, int sourceWidth, int[] indicesSorted, Boolean isNonrepeating) {
55 this.indices = indices;
56 this.sourceWidth = sourceWidth;
57 this.indicesSorted = indicesSorted;
58 this.isNonrepeating = isNonrepeating;
59 }
60
61 /**
62 * Creates a TupleMask instance that selects given positions.
63 * The mask takes ownership of the array selectedIndices, the client must not modify it afterwards.
64 *
65 * <p> indicesSorted and isNonrepeating may be OPTIONALLY given if known.
66 * @since 2.0
67 */
68 protected static TupleMask fromSelectedIndicesInternal(
69 int[] selectedIndices, int sourceArity,
70 int[] indicesSorted, Boolean isNonrepeating)
71 {
72 if (selectedIndices.length == 0) // is it nullary?
73 return new TupleMask0(sourceArity);
74
75 // is it identity?
76 boolean identity = sourceArity == selectedIndices.length;
77 if (identity) {
78 for (int k=0; k < sourceArity; ++k) {
79 if (selectedIndices[k] != k) {
80 identity = false;
81 break;
82 }
83 }
84 }
85 if (identity)
86 return new TupleMaskIdentity(selectedIndices, sourceArity);
87
88 // generic case
89 return new TupleMask(selectedIndices, sourceArity, indicesSorted, isNonrepeating);
90 }
91
92 /**
93 * Creates a TupleMask instance that selects given positions in monotonically increasing order.
94 * The mask takes ownership of the array selectedIndices, the client must not modify it afterwards.
95 * @since 2.0
96 */
97 protected static TupleMask fromSelectedMonotonicIndicesInternal(int[] selectedIndices, int sourceArity)
98 {
99 return fromSelectedIndicesInternal(selectedIndices, sourceArity, selectedIndices /* also sorted */, true);
100 }
101
102 /**
103 * Creates a TupleMask instance of the given size that maps the first 'size' elements intact
104 */
105 public static TupleMask linear(int size, int sourceWidth) {
106 if (size == sourceWidth) return new TupleMaskIdentity(sourceWidth);
107 int[] indices = constructLinearSequence(size);
108 return fromSelectedMonotonicIndicesInternal(indices, sourceWidth);
109 }
110
111 /**
112 * An array containing the first {@link size} nonnegative integers in order
113 * @since 2.0
114 */
115 protected static int[] constructLinearSequence(int size) {
116 int[] indices = new int[size];
117 for (int i = 0; i < size; i++)
118 indices[i] = i;
119 return indices;
120 }
121
122 /**
123 * Creates a TupleMask instance of the given size that maps every single element intact
124 */
125 public static TupleMask identity(int size) {
126 return new TupleMaskIdentity(size);
127 }
128
129 /**
130 * Creates a TupleMask instance of the given size that does not emit output.
131 */
132 public static TupleMask empty(int sourceWidth) {
133 return linear(0, sourceWidth);
134 }
135
136 /**
137 * Creates a TupleMask instance that maps the tuple intact save for a single element at the specified index which is
138 * omitted
139 */
140 public static TupleMask omit(int omission, int sourceWidth) {
141 int size = sourceWidth - 1;
142 int[] indices = new int[size];
143 for (int i = 0; i < omission; i++)
144 indices[i] = i;
145 for (int i = omission; i < size; i++)
146 indices[i] = i + 1;
147 return fromSelectedMonotonicIndicesInternal(indices, sourceWidth);
148 }
149
150
151 /**
152 * Creates a TupleMask instance that selects positions where keep is true
153 * @since 1.7
154 */
155 public static TupleMask fromKeepIndicators(boolean[] keep) {
156 int size = 0;
157 for (int k = 0; k < keep.length; ++k)
158 if (keep[k])
159 size++;
160 if (size == keep.length) return new TupleMaskIdentity(size);
161 int[] indices = new int[size];
162 int l = 0;
163 for (int k = 0; k < keep.length; ++k)
164 if (keep[k])
165 indices[l++] = k;
166 return fromSelectedMonotonicIndicesInternal(indices, keep.length);
167 }
168
169 /**
170 * Creates a TupleMask instance that selects given positions.
171 * @since 1.7
172 */
173 public static TupleMask fromSelectedIndices(int sourceArity, Collection<Integer> selectedIndices) {
174 int[] selected = integersToIntArray(selectedIndices);
175 return fromSelectedIndicesInternal(selected, sourceArity, null, null);
176 }
177 /**
178 * Creates a TupleMask instance that selects given positions.
179 * @since 1.7
180 */
181 public static TupleMask fromSelectedIndices(int sourceArity, int[] selectedIndices) {
182 return fromSelectedIndicesInternal(Arrays.copyOf(selectedIndices, selectedIndices.length), sourceArity, null, null);
183 }
184 /**
185 * Creates a TupleMask instance that selects non-null positions of a given tuple
186 * @since 1.7
187 */
188 public static TupleMask fromNonNullIndices(ITuple tuple) {
189 List<Integer> indices = new ArrayList<>();
190 for (int i=0; i < tuple.getSize(); i++) {
191 if (tuple.get(i) != null) {
192 indices.add(i);
193 }
194 }
195 if (indices.size() == tuple.getSize()) return new TupleMaskIdentity(indices.size());
196 return fromSelectedMonotonicIndicesInternal(integersToIntArray(indices), tuple.getSize());
197 }
198 /**
199 * @since 1.7
200 */
201 public static int[] integersToIntArray(Collection<Integer> selectedIndices) {
202 int[] selected = new int[selectedIndices.size()];
203 int k=0;
204 for (Integer integer : selectedIndices) {
205 selected[k++] = integer;
206 }
207 return selected;
208 }
209
210
211 /**
212 * Creates a TupleMask instance that moves an element from one index to other, shifting the others if neccessary.
213 */
214 public static TupleMask displace(int from, int to, int sourceWidth) {
215 if (from == to) return new TupleMaskIdentity(sourceWidth);
216 int[] indices = new int[sourceWidth];
217 for (int i = 0; i < sourceWidth; i++)
218 if (i == to)
219 indices[i] = from;
220 else if (i >= from && i < to)
221 indices[i] = i + 1;
222 else if (i > to && i <= from)
223 indices[i] = i - 1;
224 else
225 indices[i] = i;
226 return fromSelectedIndicesInternal(indices, sourceWidth, null, true);
227 }
228
229 /**
230 * Creates a TupleMask instance that selects a single element of the tuple.
231 */
232 public static TupleMask selectSingle(int selected, int sourceWidth) {
233 int[] indices = { selected };
234 return fromSelectedMonotonicIndicesInternal(indices, sourceWidth);
235 }
236
237 /**
238 * Creates a TupleMask instance that selects whatever is selected by left, and appends whatever is selected by
239 * right. PRE: left and right have the same sourcewidth
240 */
241 public static TupleMask append(TupleMask left, TupleMask right) {
242 int leftLength = left.indices.length;
243 int rightLength = right.indices.length;
244 int[] indices = new int[leftLength + rightLength];
245 for (int i = 0; i < leftLength; ++i)
246 indices[i] = left.indices[i];
247 for (int i = 0; i < rightLength; ++i)
248 indices[i + leftLength] = right.indices[i];
249 return fromSelectedIndicesInternal(indices, left.sourceWidth, null, null);
250 }
251
252 /**
253 * Generates indicesSorted from indices on demand
254 */
255 void ensureIndicesSorted() {
256 if (indicesSorted == null) {
257 indicesSorted = new int[indices.length];
258 List<Integer> list = new LinkedList<Integer>();
259 for (int i = 0; i < indices.length; ++i)
260 list.add(indices[i]);
261 java.util.Collections.sort(list);
262 int i = 0;
263 for (Integer a : list)
264 indicesSorted[i++] = a;
265 }
266 }
267
268
269
270 /**
271 * Returns the first index of the source that is not selected by the mask, or empty if all indices are selected.
272 * <p> PRE: mask indices are all different
273 * @since 2.0
274 */
275 public OptionalInt getFirstOmittedIndex() {
276 ensureIndicesSorted();
277 int column = 0;
278 while (column < getSize() && indicesSorted[column] == column) column++;
279 if (column < getSourceWidth()) return OptionalInt.of(column);
280 else return OptionalInt.empty();
281 }
282
283
284 /**
285 * Returns a selected masked value from the selected tuple.
286 * @pre: 0 <= index < getSize()
287 * @since 1.7
288 */
289 public Object getValue(ITuple original, int index) {
290 return original.get(indices[index]);
291 }
292
293 /**
294 * Sets the selected value in the original tuple based on the mask definition
295 *
296 * @pre: 0 <= index < getSize()
297 * @since 1.7
298 */
299 public void set(IModifiableTuple tuple, int index, Object value) {
300 tuple.set(indices[index], value);
301 }
302
303 /**
304 * Generates an immutable, masked view of the original tuple.
305 * <p> The new tuple will have arity {@link #getSize()},
306 * and will consist of the elements of the original tuple, at positions indicated by this mask.
307 * @since 1.7
308 */
309 public Tuple transform(ITuple original) {
310 switch (indices.length) {
311 case 0:
312 return FlatTuple0.INSTANCE;
313 case 1:
314 return new FlatTuple1(original.get(indices[0]));
315 case 2:
316 return new FlatTuple2(original.get(indices[0]), original.get(indices[1]));
317 case 3:
318 return new FlatTuple3(original.get(indices[0]), original.get(indices[1]), original.get(indices[2]));
319 case 4:
320 return new FlatTuple4(original.get(indices[0]), original.get(indices[1]), original.get(indices[2]), original.get(indices[3]));
321 default:
322 Object signature[] = new Object[indices.length];
323 for (int i = 0; i < indices.length; ++i)
324 signature[i] = original.get(indices[i]);
325 return new FlatTuple(signature);
326 }
327 }
328
329 /**
330 * @return true iff no two selected indices are the same
331 * @since 2.0
332 */
333 public boolean isNonrepeating() {
334 if (isNonrepeating == null) {
335 ensureIndicesSorted();
336 int previous = -1;
337 int i;
338 for (i = 0; i < sourceWidth && previous != indicesSorted[i]; ++i) {
339 previous = indicesSorted[i];
340 }
341 isNonrepeating = (i == sourceWidth); // if not, stopped due to detected repetition
342 }
343 return isNonrepeating;
344 }
345
346 /**
347 * Returns a tuple `result` that satisfies `this.transform(result).equals(masked)`. Positions of the result tuple
348 * that are not determined this way will be filled with null.
349 *
350 * @pre: all indices of the mask must be different, i.e {@link #isNonrepeating()} must return true
351 * @since 1.7
352 */
353 public Tuple revertFrom(ITuple masked) {
354 Object[] signature = new Object[sourceWidth];
355 for (int i = 0; i < indices.length; ++i)
356 signature[indices[i]] = masked.get(i);
357 return Tuples.flatTupleOf(signature);
358 }
359
360 /**
361 * Returns a tuple `result`, same arity as the original tuple, that satisfies
362 * `this.transform(result).equals(this.transform(tuple))`.
363 * Positions of the result tuple that are not determined this way will be filled with null.
364 * <p> In other words, a copy of the original tuple is returned,
365 * with null substituted at each position that is <em>not</em> selected by this mask.
366 *
367 * @pre: all indices of the mask must be different, i.e {@link #isNonrepeating()} must return true
368 * @since 2.1
369 */
370 public Tuple keepSelectedIndices(ITuple original) {
371 Object[] signature = new Object[sourceWidth];
372 for (int i = 0; i < indices.length; ++i)
373 signature[indices[i]] = original.get(indices[i]);
374 return Tuples.flatTupleOf(signature);
375 }
376
377 /**
378 * Generates an immutable, masked view of the original tuple.
379 * <p> The list will have arity {@link #getSize()},
380 * and will consist of the elements of the original tuple, at positions indicated by this mask.
381 */
382 public <T> List<T> transform(List<T> original) {
383 List<T> signature = new ArrayList<T>(indices.length);
384 for (int i = 0; i < indices.length; ++i)
385 signature.add(original.get(indices[i]));
386 return signature;
387 }
388
389 /**
390 * Transforms a given mask directly, instead of transforming tuples that were transformed by the other mask.
391 *
392 * @return a mask that cascades the effects this mask after the mask provided as parameter.
393 */
394 public TupleMask transform(TupleMask mask) {
395 int[] cascadeIndices = new int[indices.length];
396 for (int i = 0; i < indices.length; ++i)
397 cascadeIndices[i] = mask.indices[indices[i]];
398 return fromSelectedIndicesInternal(cascadeIndices, mask.sourceWidth, null, null);
399 }
400
401 // /**
402 // * Generates a complementer mask that maps those elements that were
403 // untouched by the original mask.
404 // * Ordering is left intact.
405 // * A Tuple is used for reference concerning possible equalities among
406 // elements.
407 // */
408 // public TupleMask complementer(Tuple reference)
409 // {
410 // HashSet<Object> touched = new HashSet<Object>();
411 // LinkedList<Integer> untouched = new LinkedList<Integer>();
412 //
413 // for (int index : indices) touched.add(reference.get(index));
414 // for (int index=0; index<reference.getSize(); ++index)
415 // {
416 // if (touched.add(reference.get(index))) untouched.addLast(index);
417 // }
418 //
419 // int[] complementer = new int[untouched.size()];
420 // int k = 0;
421 // for (Integer integer : untouched) complementer[k++] = integer;
422 // return new TupleMask(complementer, reference.getSize());
423 // }
424
425 /**
426 * Combines two substitutions. The new pattern will contain all substitutions of masked and unmasked, assuming that
427 * the elements of masked indicated by this mask are already matched against unmasked.
428 *
429 * POST: the result will start with an exact copy of unmasked
430 *
431 * @param unmasked
432 * primary pattern substitution that is left intact.
433 * @param masked
434 * secondary pattern substitution that is transformed to the end of the result.
435 * @param useInheritance
436 * whether to use inheritance or copy umasked into result instead.
437 * @param asComplementer
438 * whether this mask maps from the masked Tuple to the tail of the result or to the unmasked one.
439 * @return new pattern that is a combination of unmasked and masked.
440 */
441 public Tuple combine(Tuple unmasked, Tuple masked, boolean useInheritance, boolean asComplementer) {
442
443 int combinedLength = asComplementer ? indices.length : masked.getSize() - indices.length;
444 if (!useInheritance)
445 combinedLength += unmasked.getSize();
446 Object combined[] = new Object[combinedLength];
447
448 int cPos = 0;
449 if (!useInheritance) {
450 for (int i = 0; i < unmasked.getSize(); ++i)
451 combined[cPos++] = unmasked.get(i);
452 }
453
454 if (asComplementer) {
455 for (int i = 0; i < indices.length; ++i)
456 combined[cPos++] = masked.get(indices[i]);
457 } else {
458 ensureIndicesSorted();
459 int mPos = 0;
460 for (int i = 0; i < masked.getSize(); ++i)
461 if (mPos < indicesSorted.length && i == indicesSorted[mPos])
462 mPos++;
463 else
464 combined[cPos++] = masked.get(i);
465 }
466
467 return useInheritance ? Tuples.leftInheritanceTupleOf(unmasked, combined) : Tuples.flatTupleOf(combined);
468 }
469
470 @Override
471 public int hashCode() {
472 final int PRIME = 31;
473 int result = sourceWidth;
474 for (int i : indices)
475 result = PRIME * result + i;
476 return result;
477 }
478
479 @Override
480 public boolean equals(Object obj) {
481 if (this == obj)
482 return true;
483 if (obj == null)
484 return false;
485 if (getClass() != obj.getClass())
486 return false;
487 final TupleMask other = (TupleMask) obj;
488 if (sourceWidth != other.sourceWidth)
489 return false;
490 if (indices.length != other.indices.length)
491 return false;
492 for (int k = 0; k < indices.length; k++)
493 if (indices[k] != other.indices[k])
494 return false;
495 return true;
496 }
497
498 @Override
499 public String toString() {
500 StringBuilder s = new StringBuilder();
501 s.append("M(" + sourceWidth + "->");
502 for (int i : indices) {
503 s.append(i);
504 s.append(',');
505 }
506 s.append(')');
507 return s.toString();
508 }
509
510 /**
511 * Returns the size of the masked tuples described by this mask
512 * @since 1.7
513 */
514 public int getSize() {
515 return indices.length;
516 }
517
518 /**
519 * Returns the size of the original tuples handled by this mask
520 * @since 1.7
521 */
522 public int getSourceWidth() {
523 return sourceWidth;
524 }
525
526
527 /**
528 * @return true iff this mask is a no-op
529 * @since 2.0
530 */
531 public boolean isIdentity() {
532 // Contract: if identity mask, a specialized subclass is constructed instead
533 return false;
534 }
535
536 /**
537 * Transforms the given list by applying the mask and putting all results into a set; keeping only a single element
538 * in case of the mapping result in duplicate values.
539 *
540 * @since 1.7
541 */
542 public <T> Set<T> transformUnique(List<T> original) {
543 Set<T> signature = new HashSet<>();
544 for (int i = 0; i < indices.length; ++i)
545 signature.add(original.get(indices[i]));
546 return signature;
547 }
548
549 /**
550 * @return the list of selected indices
551 * @since 2.1
552 */
553 public List<Integer> getIndicesAsList() {
554 List<Integer> result = new ArrayList<Integer>(indices.length);
555 for (int i = 0; i < indices.length; ++i)
556 result.add(indices[i]);
557 return result;
558 }
559
560}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java
new file mode 100644
index 00000000..5a0c79ff
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Collections;
12import java.util.List;
13
14/**
15 * @author Gabor Bergmann
16 * @since 1.7
17 */
18public final class TupleMask0 extends TupleMask {
19
20 private final static int[] EMPTY_ARRAY = {};
21
22 /**
23 * PRE: indices.length == 0
24 */
25 TupleMask0(int sourceWidth) {
26 super(EMPTY_ARRAY, sourceWidth, EMPTY_ARRAY, true);
27 }
28
29 @Override
30 public <T> List<T> transform(List<T> original) {
31 return Collections.emptyList();
32 }
33
34 @Override
35 public Tuple transform(ITuple original) {
36 return Tuples.staticArityFlatTupleOf();
37 }
38
39 @Override
40 public TupleMask transform(TupleMask mask) {
41 return new TupleMask0(mask.sourceWidth);
42 }
43
44 @Override
45 public Tuple combine(Tuple unmasked, Tuple masked, boolean useInheritance, boolean asComplementer) {
46 if (asComplementer)
47 return unmasked;
48 else
49 return super.combine(unmasked, masked, useInheritance, asComplementer);
50 }
51
52 @Override
53 public boolean isIdentity() {
54 return 0 == sourceWidth;
55 }
56}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java
new file mode 100644
index 00000000..62746587
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.List;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public final class TupleMaskIdentity extends TupleMask {
18
19 TupleMaskIdentity(int sourceWidth) {
20 this(constructLinearSequence(sourceWidth), sourceWidth);
21 }
22 TupleMaskIdentity(int[] indices, int sourceWidth) {
23 super(indices, sourceWidth, indices, true);
24 }
25
26 @Override
27 public <T> List<T> transform(List<T> original) {
28 return original;
29 }
30
31 @Override
32 public Tuple transform(ITuple original) {
33 return original.toImmutable();
34 }
35
36 @Override
37 public TupleMask transform(TupleMask mask) {
38 return mask;
39 }
40
41 @Override
42 public Tuple revertFrom(ITuple masked) {
43 return masked.toImmutable();
44 }
45
46 @Override
47 public boolean isIdentity() {
48 return true;
49 }
50
51}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java
new file mode 100644
index 00000000..79193516
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider;
14
15/**
16 * @author Zoltan Ujhelyi
17 * @since 1.7
18 */
19public class TupleValueProvider implements IValueProvider {
20
21 final ITuple tuple;
22 final Map<String, Integer> indexMapping;
23
24 /**
25 * Wraps a tuple with an index mapping
26 * @param tuple
27 * @param indexMapping
28 */
29 public TupleValueProvider(ITuple tuple, Map<String, Integer> indexMapping) {
30 super();
31 this.tuple = tuple;
32 this.indexMapping = indexMapping;
33 }
34
35 @Override
36 public Object getValue(String variableName) {
37 Integer index = indexMapping.get(variableName);
38 if (index == null) {
39 throw new IllegalArgumentException(String.format("Variable %s is not present in mapping.", variableName));
40 }
41 Object value = tuple.get(index);
42 if (value == null) {
43 throw new IllegalArgumentException(String.format("Variable %s is not found using index %d.", variableName, index));
44 }
45 return value;
46 }
47
48}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java
new file mode 100644
index 00000000..5e41d7d8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java
@@ -0,0 +1,157 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11/**
12 * Common static factory utilities for tuples.
13 *
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public class Tuples {
18
19 private Tuples() {
20 // Empty utility class constructor
21 }
22
23 /**
24 * Creates a flat tuple consisting of the given elements.
25 * For low-arity tuples, specialized implementations
26 * (such as {@link FlatTuple2}) will be instantiated.
27 *
28 * <p> In case the exact arity is <i>statically</i> known,
29 * it may be more efficient for the client to instantiate
30 * the appropriate specialized implementation
31 * (via {@link #staticArityFlatTupleOf(Object, Object)} etc.
32 * or {@link #wideFlatTupleOf(Object...)}),
33 * instead of invoking this method.
34 * This method does a runtime arity check, and therefore
35 * also appropriate if the arity is determined at runtime.
36 */
37 public static Tuple flatTupleOf(Object... elements) {
38 switch (elements.length) {
39 case 0:
40 return FlatTuple0.INSTANCE;
41 case 1:
42 return new FlatTuple1(elements[0]);
43 case 2:
44 return new FlatTuple2(elements[0], elements[1]);
45 case 3:
46 return new FlatTuple3(elements[0], elements[1], elements[2]);
47 case 4:
48 return new FlatTuple4(elements[0], elements[1], elements[2], elements[3]);
49 default:
50 return new FlatTuple(elements);
51 }
52 }
53 /**
54 * Creates a left inheritance tuple that extends an ancestor tuple
55 * by the given "local" elements.
56 * For locally low-arity tuples, specialized implementations
57 * (such as {@link LeftInheritanceTuple2}) will be instantiated.
58 *
59 * <p> In case the exact arity is <i>statically</i> known,
60 * it may be more efficient for the client to instantiate
61 * the appropriate specialized implementation
62 * (via {@link #staticArityLeftInheritanceTupleOf(Object, Object)} etc.
63 * or {@link #wideLeftInheritanceTupleOf(Object...)}),
64 * instead of invoking this method.
65 * This method does a runtime arity check, and therefore
66 * also appropriate if the arity is determined at runtime.
67 */
68 public static Tuple leftInheritanceTupleOf(Tuple ancestor, Object... localElements) {
69 switch (localElements.length) {
70 case 0:
71 return ancestor;
72 case 1:
73 return new LeftInheritanceTuple1(ancestor, localElements[0]);
74 case 2:
75 return new LeftInheritanceTuple2(ancestor, localElements[0], localElements[1]);
76 case 3:
77 return new LeftInheritanceTuple3(ancestor, localElements[0], localElements[1], localElements[2]);
78 case 4:
79 return new LeftInheritanceTuple4(ancestor, localElements[0], localElements[1], localElements[2], localElements[3]);
80 default:
81 return new LeftInheritanceTuple(ancestor, localElements);
82 }
83 }
84
85 /**
86 * Creates a flat tuple consisting of no elements.
87 */
88 public static Tuple staticArityFlatTupleOf() {
89 return FlatTuple0.INSTANCE;
90 }
91 /**
92 * Creates a flat tuple consisting of the given single element.
93 */
94 public static Tuple staticArityFlatTupleOf(Object element) {
95 return new FlatTuple1(element);
96 }
97 /**
98 * Creates a flat tuple consisting of the given elements.
99 */
100 public static Tuple staticArityFlatTupleOf(Object element0, Object element1) {
101 return new FlatTuple2(element0, element1);
102 }
103 /**
104 * Creates a flat tuple consisting of the given elements.
105 */
106 public static Tuple staticArityFlatTupleOf(Object element0, Object element1, Object element2) {
107 return new FlatTuple3(element0, element1, element2);
108 }
109 /**
110 * Creates a flat tuple consisting of the given elements.
111 */
112 public static Tuple staticArityFlatTupleOf(Object element0, Object element1, Object element2, Object element3) {
113 return new FlatTuple4(element0, element1, element2, element3);
114 }
115 /**
116 * Creates a flat tuple consisting of the given elements.
117 * <p> Invoke this only if it is statically known that the tuple will be wide.
118 * Otherwise, use {@link #flatTupleOf(Object...)}.
119 */
120 public static Tuple wideFlatTupleOf(Object... elements) {
121 return new FlatTuple(elements);
122 }
123
124 /**
125 * Creates a left inheritance tuple consisting of the given single local element.
126 */
127 public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element) {
128 return new LeftInheritanceTuple1(ancestor, element);
129 }
130 /**
131 * Creates a left inheritance tuple consisting of the given local elements.
132 */
133 public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1) {
134 return new LeftInheritanceTuple2(ancestor, element0, element1);
135 }
136 /**
137 * Creates a left inheritance tuple consisting of the given local elements.
138 */
139 public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1, Object element2) {
140 return new LeftInheritanceTuple3(ancestor, element0, element1, element2);
141 }
142 /**
143 * Creates a left inheritance tuple consisting of the given local elements.
144 */
145 public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1, Object element2, Object element3) {
146 return new LeftInheritanceTuple4(ancestor, element0, element1, element2, element3);
147 }
148 /**
149 * Creates a left inheritance tuple consisting of the given local elements.
150 * <p> Invoke this only if it is statically known that the tuple will be wide.
151 * Otherwise, use {@link #leftInheritanceTupleOf(Tuple, Object...)}.
152 */
153 public static Tuple wideLeftInheritanceTupleOf(Tuple ancestor, Object... elements) {
154 return new LeftInheritanceTuple(ancestor, elements);
155 }
156
157}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java
new file mode 100644
index 00000000..f683d544
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java
@@ -0,0 +1,50 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import tools.refinery.viatra.runtime.matchers.util.Preconditions;
12
13/**
14 * This class provides a volatile tuple view with a given mask of a given tuple instance. If the masked tuple changes,
15 * the view updates as well.
16 *
17 * @author Zoltan Ujhelyi
18 * @since 1.7
19 *
20 */
21public class VolatileMaskedTuple extends VolatileTuple {
22
23 protected final TupleMask mask;
24 protected ITuple source;
25
26 public VolatileMaskedTuple(ITuple source, TupleMask mask) {
27 this.source = source;
28 this.mask = mask;
29 }
30
31 public VolatileMaskedTuple(TupleMask mask) {
32 this(null, mask);
33 }
34
35 public void updateTuple(ITuple newSource) {
36 source = newSource;
37 }
38
39 @Override
40 public Object get(int index) {
41 Preconditions.checkState(source != null, "Source tuple is not set.");
42 return mask.getValue(source, index);
43 }
44
45 @Override
46 public int getSize() {
47 return mask.getSize();
48 }
49
50}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java
new file mode 100644
index 00000000..92306c6e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java
@@ -0,0 +1,47 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.tuple;
10
11import tools.refinery.viatra.runtime.matchers.util.Preconditions;
12
13/**
14 * A masked tuple implementation that allows modifying the backing tuple.
15 * @author Zoltan Ujhelyi
16 * @since 1.7
17 *
18 */
19public class VolatileModifiableMaskedTuple extends VolatileMaskedTuple implements IModifiableTuple {
20
21 private IModifiableTuple modifiableTuple;
22
23 public VolatileModifiableMaskedTuple(IModifiableTuple source, TupleMask mask) {
24 super(source, mask);
25 modifiableTuple = source;
26 }
27
28 public VolatileModifiableMaskedTuple(TupleMask mask) {
29 this(null, mask);
30 }
31
32 @Override
33 public void updateTuple(ITuple newSource) {
34 Preconditions.checkArgument(newSource instanceof IModifiableTuple, "Provided tuple does not support updates");
35 this.updateTuple((IModifiableTuple)newSource);
36 }
37
38 public void updateTuple(IModifiableTuple newSource) {
39 super.updateTuple(newSource);
40 modifiableTuple = newSource;
41 }
42
43 @Override
44 public void set(int index, Object value) {
45 mask.set(modifiableTuple, index, value);
46 }
47}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java
new file mode 100644
index 00000000..699105a5
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java
@@ -0,0 +1,47 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017 Zoltan Ujhelyi, IncQuery Labs
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.tuple;
11
12/**
13 * Mutable tuple without explicit modification commands. In practical terms, the values stored in a volatile tuple can
14 * be changed without any notification.
15 *
16 * @author Zoltan Ujhelyi
17 * @since 1.7
18 *
19 */
20public abstract class VolatileTuple extends AbstractTuple {
21
22 @Override
23 public boolean equals(Object obj) {
24 if (this == obj)
25 return true;
26 if (obj == null)
27 return false;
28 if (!(obj instanceof ITuple))
29 return false;
30 final ITuple other = (ITuple) obj;
31 return internalEquals(other);
32 }
33
34 @Override
35 public int hashCode() {
36 return doCalcHash();
37 }
38
39 /**
40 * Creates an immutable tuple from the values stored in the tuple. The created tuple will not be updated when the
41 * current tuple changes.
42 */
43 @Override
44 public Tuple toImmutable() {
45 return Tuples.flatTupleOf(getElements());
46 }
47}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java
new file mode 100644
index 00000000..338990ab
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java
@@ -0,0 +1,48 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * The degree of accuracy of a cardinality estimate
13 * @author Gabor Bergmann
14 * @since 2.1
15 */
16public enum Accuracy {
17 EXACT_COUNT,
18 BEST_UPPER_BOUND,
19 BEST_LOWER_BOUND,
20 APPROXIMATION;
21
22 /**
23 * Partial order comparison.
24 */
25 public boolean atLeastAsPreciseAs(Accuracy other) {
26 switch (this) {
27 case EXACT_COUNT: return true;
28 case APPROXIMATION: return APPROXIMATION == other;
29 case BEST_UPPER_BOUND: return BEST_UPPER_BOUND == other || APPROXIMATION == other;
30 case BEST_LOWER_BOUND: return BEST_LOWER_BOUND == other || APPROXIMATION == other;
31 default: throw new IllegalArgumentException();
32 }
33 }
34
35 /**
36 * @return another accuracy value that is anti-monotonic to this one,
37 * i.e. an accuracy that should be used in the denominator to obtain a fraction with this accuracy
38 */
39 public Accuracy reciprocal() {
40 switch(this) {
41 case APPROXIMATION: return APPROXIMATION;
42 case BEST_UPPER_BOUND: return BEST_LOWER_BOUND;
43 case BEST_LOWER_BOUND: return BEST_UPPER_BOUND;
44 case EXACT_COUNT: return EXACT_COUNT;
45 default: throw new IllegalArgumentException();
46 }
47 }
48}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java
new file mode 100644
index 00000000..1b09aec6
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.util;
11
12/**
13 * @author Gabor Bergmann
14 * @since 1.7
15 * An instance of clearable pattern memory.
16 */
17public interface Clearable {
18 /**
19 * Clear all partial matchings stored in memory
20 *
21 */
22 void clear();
23}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java
new file mode 100644
index 00000000..590a1ec3
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java
@@ -0,0 +1,188 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collection;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15import java.util.TreeMap;
16import java.util.function.Function;
17
18/**
19 * Factory class used as an accessor to Collections implementations.
20 * @author istvanrath
21 */
22public final class CollectionsFactory
23{
24
25 /**
26 * Instantiates a new empty map.
27 * @since 1.7
28 */
29 public static <K, V> Map<K, V> createMap() {
30 return FRAMEWORK.createMap();
31 }
32
33 /**
34 * Instantiates a new map with the given initial contents.
35 * @since 1.7
36 */
37 public static <K, V> Map<K, V> createMap(Map<K, V> initial) {
38 return FRAMEWORK.createMap(initial);
39 }
40
41 /**
42 * Instantiates a new tree map.
43 * @since 2.3
44 */
45 public static <K, V> TreeMap<K, V> createTreeMap() {
46 return FRAMEWORK.createTreeMap();
47 }
48
49 /**
50 * Instantiates a new empty set.
51 * @since 1.7
52 */
53 public static <E> Set<E> createSet() {
54 return FRAMEWORK.createSet();
55 }
56
57 /**
58 * Instantiates a new set with the given initial contents.
59 * @since 1.7
60 */
61 public static <E> Set<E> createSet(Collection<E> initial) {
62 return FRAMEWORK.createSet(initial);
63 }
64
65 /**
66 * Instantiates an empty set; the key parameter is used to allow using this as a method reference as a
67 * {@link Function}, e.g. in {@link Map#computeIfAbsent(Object, Function)}.
68 *
69 * @param key
70 * the value of this parameter is ignored
71 * @since 2.0
72 */
73 public static <T> Set<T> emptySet(Object key) {
74 return FRAMEWORK.createSet();
75 }
76
77 /**
78 * Instantiates a new empty multiset.
79 * @since 1.7
80 */
81 public static <T> IMultiset<T> createMultiset() {
82 return FRAMEWORK.createMultiset();
83 }
84
85 /**
86 * Instantiates an empty multiset; the key parameter is used to allow using this as a method reference as a
87 * {@link Function}, e.g. in {@link Map#computeIfAbsent(Object, Function)}.
88 *
89 * @param key
90 * the value of this parameter is ignored
91 * @since 2.0
92 */
93 public static <T> IMultiset<T> emptyMultiset(Object key) {
94 return FRAMEWORK.createMultiset();
95 }
96
97 /**
98 * Instantiates a new empty delta bag.
99 * @since 1.7
100 */
101 public static <T> IDeltaBag<T> createDeltaBag() {
102 return FRAMEWORK.createDeltaBag();
103 }
104
105 /**
106 * Instantiates a new list that is optimized for registering observers / callbacks.
107 * @since 1.7
108 */
109 public static <O> List<O> createObserverList() {
110 return FRAMEWORK.createObserverList();
111 }
112
113 /**
114 * Instantiates a size-optimized multimap from keys to sets of values.
115 * <p>For a single key, many values can be associated according to the given bucket semantics.
116 * <p>The keys and values are stored as type fromKeys resp. ofValues;
117 * currently Object.class and Long.class are supported.
118 * @since 2.0
119 */
120 public static <K, V> IMultiLookup<K, V> createMultiLookup(
121 Class<? super K> fromKeys, MemoryType toBuckets, Class<? super V> ofValues) {
122 return FRAMEWORK.createMultiLookup(fromKeys, toBuckets, ofValues);
123 }
124
125 /**
126 * Instantiates a memory storing values.
127 * <p>For a single key, many values can be associated according to the given memory semantics.
128 * <p>The values are stored as type 'values';
129 * currently Object.class and Long.class are supported.
130 * @since 2.0
131 */
132 public static <T> IMemory<T> createMemory(
133 Class<? super T> values, MemoryType memoryType) {
134 return FRAMEWORK.createMemory(values, memoryType);
135 }
136
137 /**
138 * The type of {@link IMemory}
139 * @since 2.0
140 * TODO add delta as type
141 */
142 public enum MemoryType {
143 /**
144 * A single key-value pair is stored at most once
145 */
146 SETS,
147 /**
148 * Duplicate key-value pairs allowed
149 */
150 MULTISETS
151 }
152
153 /**
154 * The collections framework of the current configuration.
155 * @since 1.7
156 */
157 private static final ICollectionsFramework FRAMEWORK = new EclipseCollectionsFactory();
158
159 /**
160 * Interface abstracting over a collections technology that provides custom collection implementations.
161 * @since 1.7
162 */
163 public static interface ICollectionsFramework {
164
165 public abstract <K,V> Map<K,V> createMap();
166 public abstract <K,V> Map<K,V> createMap(Map<K,V> initial);
167 /**
168 * @since 2.3
169 */
170 public abstract <K, V> TreeMap<K, V> createTreeMap();
171 public abstract <E> Set<E> createSet();
172 public abstract <E> Set<E> createSet(Collection<E> initial);
173 public abstract <T> IMultiset<T> createMultiset();
174 public abstract <T> IDeltaBag<T> createDeltaBag();
175 public abstract <O> List<O> createObserverList();
176
177 /**
178 * @since 2.0
179 */
180 public abstract <K, V> IMultiLookup<K, V> createMultiLookup(
181 Class<? super K> fromKeys, MemoryType toBuckets, Class<? super V> ofValues);
182 /**
183 * @since 2.0
184 */
185 public abstract <T> IMemory<T> createMemory(Class<? super T> values, MemoryType memoryType);
186 }
187
188} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java
new file mode 100644
index 00000000..88f7ec00
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java
@@ -0,0 +1,61 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * Indicates whether a propagated update event signals the insertion or deletion of an element
13 *
14 * @author Gabor Bergmann
15 */
16public enum Direction {
17 INSERT, DELETE;
18
19 /**
20 * @since 2.4
21 */
22 public Direction opposite() {
23 switch (this) {
24 case INSERT:
25 return DELETE;
26 default:
27 return INSERT;
28 }
29 }
30
31 /**
32 * @since 2.4
33 */
34 public char asSign() {
35 switch (this) {
36 case INSERT:
37 return '+';
38 default:
39 return '-';
40 }
41 }
42
43 /**
44 * Returns the direction that is the product of this direction and the other direction.
45 *
46 * DELETE x DELETE = INSERT
47 * DELETE x INSERT = DELETE
48 * INSERT x DELETE = DELETE
49 * INSERT x INSERT = INSERT
50 * @since 2.4
51 */
52 public Direction multiply(final Direction other) {
53 switch (this) {
54 case DELETE:
55 return other.opposite();
56 default:
57 return other;
58 }
59 }
60
61}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java
new file mode 100644
index 00000000..e24b2448
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9
10package tools.refinery.viatra.runtime.matchers.util;
11
12import java.util.Iterator;
13import java.util.Set;
14import java.util.function.BiConsumer;
15
16import org.eclipse.collections.impl.map.mutable.primitive.ObjectIntHashMap;
17
18/**
19 * Eclipse Collections-based multiset for tuples. Can contain duplicate occurrences of the same matching.
20 *
21 * <p>Inherits Eclipse Collections' Object-to-Int primitive hashmap and counts the number of occurrences of each value.
22 * Element is deleted if # of occurences drops to 0.
23 *
24 * @author Gabor Bergmann.
25 * @since 1.7
26 * @noreference
27 */
28public abstract class EclipseCollectionsBagMemory<T> extends ObjectIntHashMap<T> implements IMemory<T> {
29
30 public EclipseCollectionsBagMemory() {
31 super();
32 }
33
34 @Override
35 public int getCount(T value) {
36 return super.getIfAbsent(value, 0);
37 }
38 @Override
39 public int getCountUnsafe(Object value) {
40 return super.getIfAbsent(value, 0);
41 }
42 @Override
43 public boolean containsNonZero(T value) {
44 return super.containsKey(value);
45 }
46 @Override
47 public boolean containsNonZeroUnsafe(Object value) {
48 return super.containsKey(value);
49 }
50
51 @Override
52 public void clearAllOf(T value) {
53 super.remove(value);
54 }
55
56
57 @Override
58 public Iterator<T> iterator() {
59 return super.keySet().iterator();
60 }
61
62 @Override
63 public String toString() {
64 return "TM" + super.toString();
65 }
66
67 @Override
68 public Set<T> distinctValues() {
69 return super.keySet();
70 }
71
72 @Override
73 public void forEachEntryWithMultiplicities(BiConsumer<T, Integer> entryConsumer) {
74 super.forEachKeyValue(entryConsumer::accept);
75 }
76
77 @Override
78 public int hashCode() {
79 return IMemoryView.hashCode(this);
80 }
81 @Override
82 public boolean equals(Object obj) {
83 return IMemoryView.equals(this, obj);
84 }
85
86}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java
new file mode 100644
index 00000000..94ec33cd
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java
@@ -0,0 +1,41 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * @author Gabor Bergmann
13 * @since 1.7
14 */
15public class EclipseCollectionsDeltaBag<T> extends EclipseCollectionsBagMemory<T> implements IDeltaBag<T> {
16
17 @Override
18 public boolean addOne(T value) {
19 return addSigned(value, +1);
20 }
21
22 @Override
23 public boolean addSigned(T value, int count) {
24 int oldCount = super.getIfAbsent(value, 0);
25 int newCount = oldCount + count;
26
27 boolean becomesZero = newCount == 0;
28 if (becomesZero)
29 super.removeKey(value);
30 else
31 super.put(value, newCount);
32
33 return becomesZero || oldCount == 0;
34 }
35
36
37 @Override
38 public boolean removeOne(T value) {
39 return addSigned(value, -1);
40 }
41}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java
new file mode 100644
index 00000000..5a623c9b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java
@@ -0,0 +1,159 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16import java.util.TreeMap;
17
18import org.eclipse.collections.api.map.MutableMap;
19import org.eclipse.collections.impl.factory.Maps;
20import org.eclipse.collections.impl.factory.Sets;
21import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.ICollectionsFramework;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
23
24/**
25 * @author Gabor Bergmann
26 * @since 1.7
27 * @noreference This class is not intended to be referenced by clients.
28 */
29public class EclipseCollectionsFactory implements ICollectionsFramework {
30
31 @Override
32 public <K, V> Map<K, V> createMap() {
33 return Maps.mutable.empty();
34 }
35
36 @Override
37 public <K, V> Map<K, V> createMap(Map<K, V> initial) {
38 MutableMap<K, V> result = Maps.mutable.ofInitialCapacity(initial.size());
39 result.putAll(initial);
40 return result;
41 }
42
43 @Override
44 public <K, V> TreeMap<K, V> createTreeMap() {
45 // eclipse collections is doing the same
46 return new TreeMap<>();
47 }
48
49 @Override
50 public <E> Set<E> createSet() {
51 return Sets.mutable.empty();
52 }
53
54 @Override
55 public <E> Set<E> createSet(Collection<E> initial) {
56 return Sets.mutable.ofAll(initial);
57 }
58
59 @Override
60 public <T> IMultiset<T> createMultiset() {
61 return new EclipseCollectionsMultiset<T>();
62 }
63
64 @Override
65 public <T> IDeltaBag<T> createDeltaBag() {
66 return new EclipseCollectionsDeltaBag<T>();
67 }
68
69 @Override
70 public <O> List<O> createObserverList() {
71 return new ArrayList<O>(1); // keep concurrent modification exceptions for error detection
72 // Lists.mutable.empty
73
74 }
75
76 @Override
77 @SuppressWarnings({ "unchecked", "rawtypes" })
78 public <K, V> IMultiLookup<K, V> createMultiLookup(
79 Class<? super K> fromKeys,
80 MemoryType toBuckets,
81 Class<? super V> ofValues)
82 {
83 boolean longKeys = Long.class.equals(fromKeys);
84 boolean objectKeys = Object.class.equals(fromKeys);
85 if (! (longKeys || objectKeys)) throw new IllegalArgumentException(fromKeys.getName());
86 boolean longValues = Long.class.equals(ofValues);
87 boolean objectValues = Object.class.equals(ofValues);
88 if (! (longValues || objectValues)) throw new IllegalArgumentException(ofValues.getName());
89
90 if (longKeys) { // K == java.lang.Long
91 if (longValues) { // V == java.lang.Long
92 switch(toBuckets) {
93 case MULTISETS:
94 return (IMultiLookup<K, V>) new EclipseCollectionsMultiLookup.FromLongs.ToMultisets.OfLongs();
95 case SETS:
96 return (IMultiLookup<K, V>) new EclipseCollectionsMultiLookup.FromLongs.ToSets.OfLongs();
97 default:
98 throw new IllegalArgumentException(toBuckets.toString());
99 }
100 } else { // objectValues
101 switch(toBuckets) {
102 case MULTISETS:
103 return new EclipseCollectionsMultiLookup.FromLongs.ToMultisets.OfObjects();
104 case SETS:
105 return new EclipseCollectionsMultiLookup.FromLongs.ToSets.OfObjects();
106 default:
107 throw new IllegalArgumentException(toBuckets.toString());
108 }
109 }
110 } else { // objectKeys
111 if (longValues) { // V == java.lang.Long
112 switch(toBuckets) {
113 case MULTISETS:
114 return new EclipseCollectionsMultiLookup.FromObjects.ToMultisets.OfLongs();
115 case SETS:
116 return new EclipseCollectionsMultiLookup.FromObjects.ToSets.OfLongs();
117 default:
118 throw new IllegalArgumentException(toBuckets.toString());
119 }
120 } else { // objectValues
121 switch(toBuckets) {
122 case MULTISETS:
123 return new EclipseCollectionsMultiLookup.FromObjects.ToMultisets.OfObjects();
124 case SETS:
125 return new EclipseCollectionsMultiLookup.FromObjects.ToSets.OfObjects();
126 default:
127 throw new IllegalArgumentException(toBuckets.toString());
128 }
129 }
130 }
131 }
132
133 @Override
134 @SuppressWarnings("unchecked")
135 public <T> IMemory<T> createMemory(Class<? super T> values, MemoryType memoryType) {
136 if (Long.class.equals(values)) { // T == java.lang.Long
137 switch(memoryType) {
138 case MULTISETS:
139 return (IMemory<T>) new EclipseCollectionsLongMultiset();
140 case SETS:
141 return (IMemory<T>) new EclipseCollectionsLongSetMemory();
142 default:
143 throw new IllegalArgumentException(memoryType.toString());
144 }
145 } else { // objectValues
146 switch(memoryType) {
147 case MULTISETS:
148 return new EclipseCollectionsMultiset<>();
149 case SETS:
150 return new EclipseCollectionsSetMemory<>();
151 default:
152 throw new IllegalArgumentException(memoryType.toString());
153 }
154 }
155 }
156
157
158
159}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java
new file mode 100644
index 00000000..88773c5d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java
@@ -0,0 +1,150 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Iterator;
12import java.util.Set;
13import java.util.function.BiConsumer;
14
15import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap;
16
17/**
18 * @author Gabor Bergmann
19 * @since 2.0
20 * <p> TODO refactor common methods with {@link EclipseCollectionsMultiset}
21 * <p> TODO refactor into LongBagMemory etc.
22 */
23public class EclipseCollectionsLongMultiset extends LongIntHashMap implements IMultiset<Long> {
24
25 @Override
26 public boolean addOne(Long value) {
27 int oldCount = super.getIfAbsent(value, 0);
28
29 super.put(value, oldCount + 1);
30
31 return oldCount == 0;
32 }
33
34 @Override
35 public boolean addSigned(Long value, int count) {
36 int oldCount = super.getIfAbsent(value, 0);
37 int newCount = oldCount + count;
38
39 boolean becomesZero = newCount == 0;
40 if (newCount < 0)
41 throw new IllegalStateException(String.format(
42 "Cannot remove %d occurrences of value '%s' as only %d would remain in %s",
43 count, value, newCount, this));
44 else if (becomesZero)
45 super.removeKey(value);
46 else // (newCount > 0)
47 super.put(value, newCount);
48
49 return becomesZero || oldCount == 0;
50 }
51
52 @Override
53 public boolean removeOne(Long value) {
54 return removeOneInternal(value, true);
55 }
56 /**
57 * @since 2.3
58 */
59 @Override
60 public boolean removeOneOrNop(Long value) {
61 return removeOneInternal(value, false);
62 }
63
64
65 /**
66 * @since 2.3
67 */
68 protected boolean removeOneInternal(Long value, boolean throwIfImpossible) {
69 int oldCount = super.getIfAbsent(value, 0);
70 if (oldCount == 0) {
71 if (throwIfImpossible) throw new IllegalStateException(String.format(
72 "Cannot remove value '%s' that is not contained in %s",
73 value, this));
74 else return false;
75 }
76
77 int rest = oldCount - 1;
78 boolean empty = rest == 0;
79
80 if (!empty) {
81 super.put(value, rest);
82 } else {
83 super.remove(value);
84 }
85
86 return empty;
87 }
88
89 @Override
90 public void clearAllOf(Long value) {
91 super.remove(value);
92 }
93
94 @Override
95 public int getCount(Long value) {
96 return super.getIfAbsent(value, 0);
97 }
98 @Override
99 public int getCountUnsafe(Object value) {
100 return value instanceof Long ? getCount((Long) value) : 0;
101 }
102
103 @Override
104 public boolean containsNonZero(Long value) {
105 return super.containsKey(value);
106 }
107
108 @Override
109 public boolean containsNonZeroUnsafe(Object value) {
110 return value instanceof Long && containsNonZero((Long) value);
111 }
112
113 @Override
114 public Iterator<Long> iterator() {
115 return EclipseCollectionsLongSetMemory.iteratorOf(super.keySet());
116 }
117
118 @Override
119 public boolean addPositive(Long value, int count) {
120 if (count < 0) {
121 throw new IllegalArgumentException("The count value must be positive!");
122 }
123
124 int oldCount = super.getIfAbsent(value, 0);
125
126 super.put(value, oldCount + count);
127
128 return oldCount == 0;
129 }
130
131 @Override
132 public Set<Long> distinctValues() {
133 return new EclipseCollectionsLongSetMemory.SetWrapper(super.keySet());
134 }
135
136 @Override
137 public void forEachEntryWithMultiplicities(BiConsumer<Long, Integer> entryConsumer) {
138 super.forEachKeyValue(entryConsumer::accept);
139 }
140
141 @Override
142 public int hashCode() {
143 return IMemoryView.hashCode(this);
144 }
145 @Override
146 public boolean equals(Object obj) {
147 return IMemoryView.equals(this, obj);
148 }
149
150}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java
new file mode 100644
index 00000000..fceb54fc
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java
@@ -0,0 +1,212 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.Set;
14
15import org.eclipse.collections.api.LongIterable;
16import org.eclipse.collections.api.iterator.LongIterator;
17import org.eclipse.collections.api.set.primitive.LongSet;
18import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;
19
20/**
21 * @author Gabor Bergmann
22 * @since 2.0
23 */
24public class EclipseCollectionsLongSetMemory extends LongHashSet implements ISetMemory<Long> {
25
26 @Override
27 public boolean addOne(Long value) {
28 return super.add(value);
29 }
30
31 @Override
32 public boolean addSigned(Long value, int count) {
33 if (count == 1) return addOne(value);
34 else if (count == -1) return removeOne(value);
35 else throw new IllegalStateException();
36 }
37
38 @Override
39 public boolean removeOne(Long value) {
40 // Kept for binary compatibility
41 return ISetMemory.super.removeOne(value);
42 }
43
44 /**
45 * @since 2.3
46 */
47 @Override
48 public boolean removeOneOrNop(Long value) {
49 return super.remove(value);
50 }
51
52 @Override
53 public void clearAllOf(Long value) {
54 super.remove(value);
55 }
56
57 @Override
58 public int getCount(Long value) {
59 return super.contains(value) ? 1 : 0;
60 }
61
62 @Override
63 public int getCountUnsafe(Object value) {
64 return value instanceof Long ? getCount((Long) value) : 0;
65 }
66
67 @Override
68 public boolean containsNonZero(Long value) {
69 return super.contains(value);
70 }
71
72 @Override
73 public boolean containsNonZeroUnsafe(Object value) {
74 return value instanceof Long && containsNonZero((Long) value);
75 }
76
77 @Override
78 public Iterator<Long> iterator() {
79 return iteratorOf(this);
80 }
81
82 @Override
83 public Set<Long> distinctValues() {
84 return new SetWrapper(this);
85 }
86
87 @Override
88 public boolean isEmpty() {
89 return super.isEmpty();
90 }
91
92 /**
93 * Helper for iterating a LongIterable
94 */
95 public static Iterator<Long> iteratorOf(LongIterable wrapped) {
96 return new Iterator<Long>() {
97 LongIterator longIterator = wrapped.longIterator();
98
99 @Override
100 public boolean hasNext() {
101 return longIterator.hasNext();
102 }
103
104 @Override
105 public Long next() {
106 return longIterator.next();
107 }
108 };
109 }
110
111 @Override
112 public int hashCode() {
113 return IMemoryView.hashCode(this);
114 }
115 @Override
116 public boolean equals(Object obj) {
117 return IMemoryView.equals(this, obj);
118 }
119
120
121 /**
122 * Helper that presents a primitive collection as a Set view
123 * @author Gabor Bergmann
124 */
125 public static final class SetWrapper implements Set<Long> {
126 private LongSet wrapped;
127
128 /**
129 * @param wrapped
130 */
131 public SetWrapper(LongSet wrapped) {
132 this.wrapped = wrapped;
133 }
134
135 @Override
136 public int size() {
137 return wrapped.size();
138 }
139
140 @Override
141 public boolean isEmpty() {
142 return wrapped.isEmpty();
143 }
144
145 @Override
146 public boolean contains(Object o) {
147 return o instanceof Long && wrapped.contains((Long)o);
148 }
149
150 @Override
151 public Iterator<Long> iterator() {
152 return iteratorOf(wrapped);
153 }
154
155 @Override
156 public boolean containsAll(Collection<?> c) {
157 for (Object object : c) {
158 if (contains(object))
159 return true;
160 }
161 return false;
162 }
163
164 @Override
165 public Object[] toArray() {
166 return toArray(new Long[wrapped.size()]);
167 }
168
169 @Override
170 @SuppressWarnings("unchecked")
171 public <T> T[] toArray(T[] a) {
172 int k = 0;
173 LongIterator iterator = wrapped.longIterator();
174 while (iterator.hasNext())
175 a[k++] = (T) Long.valueOf(iterator.next());
176 return a;
177 }
178
179 @Override
180 public boolean add(Long e) {
181 throw new UnsupportedOperationException();
182 }
183
184 @Override
185 public boolean remove(Object o) {
186 throw new UnsupportedOperationException();
187 }
188
189 @Override
190 public boolean addAll(Collection<? extends Long> c) {
191 throw new UnsupportedOperationException();
192 }
193
194 @Override
195 public boolean retainAll(Collection<?> c) {
196 throw new UnsupportedOperationException();
197 }
198
199 @Override
200 public boolean removeAll(Collection<?> c) {
201 throw new UnsupportedOperationException();
202 }
203
204 @Override
205 public void clear() {
206 throw new UnsupportedOperationException();
207 }
208
209
210 }
211
212}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java
new file mode 100644
index 00000000..394135c9
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java
@@ -0,0 +1,226 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import org.eclipse.collections.impl.map.mutable.UnifiedMap;
12import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
13import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedMultiset;
14import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedSet;
15
16import java.util.Set;
17import java.util.stream.Stream;
18
19
20
21/**
22 * Eclipse Collections-based realizations of {@link IMultiLookup}
23 *
24 * @author Gabor Bergmann
25 * @since 2.0
26 */
27class EclipseCollectionsMultiLookup {
28
29 private EclipseCollectionsMultiLookup() {/* Hidden utility class constructor */}
30
31 private static class MarkedSetImpl<Value> extends EclipseCollectionsSetMemory<Value> implements MarkedMemory.MarkedSet<Value> {}
32 private static class MarkedMultisetImpl<Value> extends EclipseCollectionsMultiset<Value> implements MarkedMemory.MarkedMultiset<Value> {}
33 private static class MarkedLongSetImpl extends EclipseCollectionsLongSetMemory implements MarkedMemory.MarkedSet<Long> {}
34 private static class MarkedLongMultisetImpl extends EclipseCollectionsLongMultiset implements MarkedMemory.MarkedMultiset<Long> {}
35
36 public abstract static class FromObjects<Key, Value, Bucket extends MarkedMemory<Value>>
37 extends UnifiedMap<Key, Object> implements IMultiLookupAbstract<Key, Value, Bucket> {
38
39 @Override
40 public boolean equals(Object obj) {
41 return IMultiLookup.equals(this, obj);
42 }
43 @Override
44 public int hashCode() {
45 return IMultiLookup.hashCode(this);
46 }
47
48
49 @Override
50 public Object lowLevelPutIfAbsent(Key key, Value value) {
51 return super.putIfAbsent(key, value);
52 }
53
54 @Override
55 public Object lowLevelGet(Key key) {
56 return super.get(key);
57 }
58
59 @Override
60 public Object lowLevelGetUnsafe(Object key) {
61 return super.get(key);
62 }
63
64 @Override
65 public Object lowLevelRemove(Key key) {
66 return super.remove(key);
67 }
68
69 @Override
70 public void lowLevelPut(Key key, Object valueOrBucket) {
71 super.put(key, valueOrBucket);
72 }
73 @Override
74 public Iterable<Object> lowLevelValues() {
75 return super.values();
76 }
77 @Override
78 public Set<Key> lowLevelKeySet() {
79 return super.keySet();
80 }
81 @Override
82 public int lowLevelSize() {
83 return super.size();
84 }
85
86 @Override
87 public Stream<Key> distinctKeysStream() {
88 // may be more efficient than the default spliterator
89 return super.keySet().stream();
90 }
91
92 public abstract static class ToSets<Key, Value> extends FromObjects<Key, Value, MarkedSet<Value>>
93 implements IMultiLookupAbstract.ToSetsAbstract<Key, Value>
94 {
95 public static class OfObjects<Key, Value> extends ToSets<Key, Value> {
96 @Override
97 public MarkedSet<Value> createMarkedSet() {
98 return new MarkedSetImpl<Value>();
99 }
100 }
101
102 public static class OfLongs<Key> extends ToSets<Key, Long> {
103 @Override
104 public MarkedSet<Long> createMarkedSet() {
105 return new MarkedLongSetImpl();
106 }
107 }
108
109 }
110
111 public abstract static class ToMultisets<Key, Value> extends FromObjects<Key, Value, MarkedMultiset<Value>>
112 implements IMultiLookupAbstract.ToMultisetsAbstract<Key, Value>
113 {
114 public static class OfObjects<Key, Value> extends ToMultisets<Key, Value> {
115 @Override
116 public MarkedMultiset<Value> createMarkedMultiset() {
117 return new MarkedMultisetImpl<Value>();
118 }
119 }
120
121 public static class OfLongs<Key> extends ToMultisets<Key, Long> {
122 @Override
123 public MarkedMultiset<Long> createMarkedMultiset() {
124 return new MarkedLongMultisetImpl();
125 }
126 }
127
128 }
129
130 }
131
132 public abstract static class FromLongs<Value, Bucket extends MarkedMemory<Value>>
133 extends LongObjectHashMap<Object> implements IMultiLookupAbstract<Long, Value, Bucket> {
134
135 @Override
136 public boolean equals(Object obj) {
137 return IMultiLookup.equals(this, obj);
138 }
139 @Override
140 public int hashCode() {
141 return IMultiLookup.hashCode(this);
142 }
143
144 @Override
145 public Object lowLevelPutIfAbsent(Long key, Value value) {
146 Object old = super.get(key);
147 if (old == null) super.put(key, value);
148 return old;
149 }
150
151 @Override
152 public Object lowLevelGet(Long key) {
153 return super.get(key);
154 }
155
156 @Override
157 public Object lowLevelGetUnsafe(Object key) {
158 return key instanceof Long ? super.get((Long)key) : null;
159 }
160
161 @Override
162 public Object lowLevelRemove(Long key) {
163 return super.remove(key);
164 }
165
166 @Override
167 public void lowLevelPut(Long key, Object valueOrBucket) {
168 super.put(key, valueOrBucket);
169 }
170 @Override
171 public Iterable<Object> lowLevelValues() {
172 return super.values();
173 }
174 @Override
175 public int lowLevelSize() {
176 return super.size();
177 }
178 @Override
179 public Iterable<Long> lowLevelKeySet() {
180 return () -> EclipseCollectionsLongSetMemory.iteratorOf(FromLongs.super.keysView());
181 }
182
183 public abstract static class ToSets<Value> extends FromLongs<Value, MarkedSet<Value>>
184 implements IMultiLookupAbstract.ToSetsAbstract<Long, Value>
185 {
186 public static class OfObjects<Value> extends ToSets<Value> {
187 @Override
188 public MarkedSet<Value> createMarkedSet() {
189 return new MarkedSetImpl<Value>();
190 }
191 }
192
193 public static class OfLongs extends ToSets<Long> {
194 @Override
195 public MarkedSet<Long> createMarkedSet() {
196 return new MarkedLongSetImpl();
197 }
198 }
199
200 }
201
202 public abstract static class ToMultisets<Value> extends FromLongs<Value, MarkedMultiset<Value>>
203 implements IMultiLookupAbstract.ToMultisetsAbstract<Long, Value>
204 {
205 public static class OfObjects<Value> extends ToMultisets<Value> {
206 @Override
207 public MarkedMultiset<Value> createMarkedMultiset() {
208 return new MarkedMultisetImpl<Value>();
209 }
210 }
211
212 public static class OfLongs extends ToMultisets<Long> {
213 @Override
214 public MarkedMultiset<Long> createMarkedMultiset() {
215 return new MarkedLongMultisetImpl();
216 }
217 }
218
219 }
220
221 }
222
223
224}
225
226
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java
new file mode 100644
index 00000000..46977c8b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * @author Gabor Bergmann
13 * @since 1.7
14 */
15public class EclipseCollectionsMultiset<T> extends EclipseCollectionsBagMemory<T> implements IMultiset<T> {
16
17 @Override
18 public boolean addOne(T value) {
19 int oldCount = super.getIfAbsent(value, 0);
20
21 super.put(value, oldCount + 1);
22
23 return oldCount == 0;
24 }
25
26 @Override
27 public boolean addPositive(T value, int count) {
28 if (count < 0) {
29 throw new IllegalArgumentException("The count value must be positive!");
30 }
31
32 int oldCount = super.getIfAbsent(value, 0);
33
34 super.put(value, oldCount + count);
35
36 return oldCount == 0;
37 }
38
39 @Override
40 public boolean addSigned(T value, int count) {
41 int oldCount = super.getIfAbsent(value, 0);
42 int newCount = oldCount + count;
43
44 boolean becomesZero = newCount == 0;
45 if (newCount < 0)
46 throw new IllegalStateException(String.format(
47 "Cannot remove %d occurrences of value '%s' as only %d would remain in %s",
48 count, value, newCount, this));
49 else if (becomesZero)
50 super.removeKey(value);
51 else // (newCount > 0)
52 super.put(value, newCount);
53
54 return becomesZero || oldCount == 0;
55 }
56
57
58 @Override
59 public boolean removeOne(T value) {
60 return removeOneInternal(value, true);
61 }
62
63 @Override
64 public boolean removeOneOrNop(T value) {
65 return removeOneInternal(value, false);
66 }
67
68 /**
69 * @since 2.3
70 */
71 protected boolean removeOneInternal(T value, boolean throwIfImpossible) {
72 int oldCount = super.getIfAbsent(value, 0);
73 if (oldCount == 0) {
74 if (throwIfImpossible) throw new IllegalStateException(String.format(
75 "Cannot remove value '%s' that is not contained in %s",
76 value, this));
77 else return false;
78 }
79
80 int rest = oldCount - 1;
81 boolean empty = rest == 0;
82
83 if (!empty) {
84 super.put(value, rest);
85 } else {
86 super.remove(value);
87 }
88
89 return empty;
90 }
91
92
93}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java
new file mode 100644
index 00000000..92f65246
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Set;
12
13import org.eclipse.collections.impl.set.mutable.UnifiedSet;
14
15/**
16 * @author Gabor Bergmann
17 * @since 2.0
18 */
19public class EclipseCollectionsSetMemory<Value> extends UnifiedSet<Value> implements ISetMemory<Value> {
20 @Override
21 public int getCount(Value value) {
22 return super.contains(value) ? 1 : 0;
23 }
24 @Override
25 public int getCountUnsafe(Object value) {
26 return super.contains(value) ? 1 : 0;
27 }
28 @Override
29 public boolean containsNonZero(Value value) {
30 return super.contains(value);
31 }
32
33 @Override
34 public boolean containsNonZeroUnsafe(Object value) {
35 return super.contains(value);
36 }
37
38 @Override
39 public boolean addOne(Value value) {
40 return super.add(value);
41 }
42
43 @Override
44 public boolean addSigned(Value value, int count) {
45 if (count == 1) return addOne(value);
46 else if (count == -1) return removeOne(value);
47 else throw new IllegalStateException();
48 }
49
50 @Override
51 public boolean removeOne(Value value) {
52 // Kept for binary compatibility
53 return ISetMemory.super.removeOne(value);
54 }
55
56 @Override
57 public boolean removeOneOrNop(Value value) {
58 return super.remove(value);
59 }
60
61 @Override
62 public void clearAllOf(Value value) {
63 super.remove(value);
64 }
65
66 @Override
67 public Set<Value> distinctValues() {
68 return this;
69 }
70
71 @Override
72 public Value theContainedVersionOf(Value value) {
73 return super.get(value);
74 }
75
76 @Override
77 @SuppressWarnings("unchecked")
78 public Value theContainedVersionOfUnsafe(Object value) {
79 if (super.contains(value))
80 return super.get((Value)value);
81 else return null;
82 }
83
84 @Override
85 public int hashCode() {
86 return IMemoryView.hashCode(this);
87 }
88 @Override
89 public boolean equals(Object obj) {
90 return IMemoryView.equals(this, obj);
91 }
92
93
94}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java
new file mode 100644
index 00000000..a17b3a3f
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collections;
12import java.util.Iterator;
13import java.util.Set;
14
15/**
16 * A singleton immutable empty memory.
17 * @author Gabor Bergmann
18 * @since 2.0
19 *
20 */
21public class EmptyMemory<T> implements IMemoryView<T> {
22
23 @SuppressWarnings("rawtypes")
24 private static final EmptyMemory INSTANCE = new EmptyMemory();
25
26 @SuppressWarnings("unchecked")
27 public static <T> EmptyMemory<T> instance() {
28 return INSTANCE;
29 }
30
31
32
33 /**
34 * Singleton; hidden constructor
35 */
36 private EmptyMemory() {
37 super();
38 }
39
40 @Override
41 public Iterator<T> iterator() {
42 return Collections.<T>emptySet().iterator();
43 }
44
45 @Override
46 public int getCount(T value) {
47 return 0;
48 }
49
50 @Override
51 public int getCountUnsafe(Object value) {
52 return 0;
53 }
54
55 @Override
56 public boolean containsNonZero(T value) {
57 return false;
58 }
59
60 @Override
61 public boolean containsNonZeroUnsafe(Object value) {
62 return false;
63 }
64
65 @Override
66 public int size() {
67 return 0;
68 }
69
70 @Override
71 public boolean isEmpty() {
72 return true;
73 }
74
75 @Override
76 public Set<T> distinctValues() {
77 return Collections.emptySet();
78 }
79
80 @Override
81 public int hashCode() {
82 return IMemoryView.hashCode(this);
83 }
84 @Override
85 public boolean equals(Object obj) {
86 return IMemoryView.equals(this, obj);
87 }
88
89 @Override
90 public String toString() {
91 return "{}";
92 }
93}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java
new file mode 100644
index 00000000..8c2e54ad
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java
@@ -0,0 +1,32 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.function.Supplier;
12
13/**
14 * A cache is a simple key-value pair that stores calculated values for specific key objects
15 *
16 * <p>
17 * <b>NOTE</b> These caches are not expected to be used outside query backend implementations
18 *
19 * @author Zoltan Ujhelyi
20 * @since 1.7
21 * @noreference This interface is not intended to be referenced by clients.
22 */
23public interface ICache {
24
25 /**
26 * Return a selected value for the key object. If the value is not available in the cache yet, the given provider is
27 * called once
28 * @since 2.0
29 */
30 <T> T getValue(Object key, Class<? extends T> clazz, Supplier<T> valueProvider);
31
32} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java
new file mode 100644
index 00000000..99a4cb3b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java
@@ -0,0 +1,26 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * An {@link IMemory} that represents the difference between two states of a set or {@link IMultiset}, and therefore
13 * may contain values with a negative multiplicity.
14 *
15 * @author Gabor Bergmann
16 * @since 1.7
17 */
18public interface IDeltaBag<T> extends IMemory<T> {
19
20 @Override
21 default boolean removeOneOrNop(T value) {
22 // makes no difference for delta bags
23 return removeOne(value);
24 }
25
26}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java
new file mode 100644
index 00000000..ea788e53
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * A memory containing a positive or negative number of equal() copies for some values.
13 * During iterations, each distinct value is iterated only once.
14 *
15 * <p> Refined by: <ul>
16 * <li>{@link IMultiset}, which always contains values with a nonnegative multiplicity. </li>
17 * <li>{@link IDeltaBag}, which may contain values with negative multiplicity. </li>
18 * <li>{@link ISetMemory}, which is just a set (allowed multiplicities: 0 and 1). </li>
19 * </ul>
20 *
21 * @author Gabor Bergmann
22 * @since 1.7
23 * @noimplement This interface is not intended to be implemented by clients.
24 */
25public interface IMemory<T> extends IMemoryView<T>, Clearable {
26
27 /**
28 * Adds one value occurrence to the memory.
29 *
30 * @return true if the tuple was not present before in the memory, or
31 * (in case of {@link IDeltaBag}) is no longer present in the memory
32 */
33 boolean addOne(T value);
34
35 /**
36 * Adds the given number of occurrences to the memory. The count value may or may not be negative.
37 * <p> Precondition if {@link IMultiset}: at least the given amount of occurrences exist, if count is negative.
38 * <p> Precondition if {@link ISetMemory}: count is +1 or -1, the latter is only allowed if the set contains the value.
39 *
40 * @param count
41 * the number of occurrences
42 * @return true if the tuple was not present before in the memory, or is no longer present in the memory
43 * @throws IllegalStateException if {@link IMultiset} or {@link ISetMemory} and the number of occurrences in the memory would underflow to negative
44 */
45 boolean addSigned(T value, int count);
46
47 /**
48 * Removes one occurrence of the given value from the memory.
49 * <p> Precondition if {@link IMultiset} or {@link ISetMemory}: the value must have a positive amount of occurrences in the memory.
50 *
51 * @return true if this was the the last occurrence of the value, or
52 * (in case of {@link IDeltaBag}) is the first negative occurrence of the value
53 * @throws IllegalStateException if {@link IMultiset} or {@link ISetMemory} and value had no occurrences in the memory
54 */
55 boolean removeOne(T value);
56
57 /**
58 * Removes one occurrence of the given value from the memory, if possible.
59 *
60 * <p> Memory is unchanged and false is returned if
61 * {@link IMultiset} or {@link ISetMemory} and value had no occurrences in the memory
62 *
63 * @return true if this was the the last occurrence of the value, or
64 * (in case of {@link IDeltaBag}) is the first negative occurrence of the value
65 *
66 * @since 2.3
67 */
68 boolean removeOneOrNop(T value);
69
70 /**
71 * Removes all occurrences of the given value from the memory.
72 */
73 void clearAllOf(T value);
74
75 /**
76 * Empties out the memory.
77 */
78 @Override
79 void clear();
80
81} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java
new file mode 100644
index 00000000..add575c6
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java
@@ -0,0 +1,205 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Iterator;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Set;
15import java.util.function.BiConsumer;
16import java.util.stream.Stream;
17import java.util.stream.StreamSupport;
18
19/**
20 * A read-only view on a memory containing a positive or negative number of equal() copies for some values.
21 * During iterations, each distinct value is iterated only once.
22 *
23 * <p> See {@link IMemory}.
24 *
25 * <p> Implementors must provide semantic (not identity-based) hashCode() and equals() using the static helpers {@link #hashCode(IMemoryView)} and {@link #equals(IMemoryView, Object)} here.
26 *
27 * @author Gabor Bergmann
28 *
29 * @since 2.0
30 */
31public interface IMemoryView<T> extends Iterable<T> {
32
33 /**
34 * Returns the number of occurrences of the given value.
35 *
36 * @return the number of occurrences
37 */
38 int getCount(T value);
39
40 /**
41 * Returns the number of occurrences of the given value (which may be of any type).
42 *
43 * @return the number of occurrences
44 */
45 int getCountUnsafe(Object value);
46
47 /**
48 * @return true if the given value is contained with a nonzero multiplicity
49 */
50 boolean containsNonZero(T value);
51
52 /**
53 * @return true if the given value (which may be of any type) is contained with a nonzero multiplicity
54 */
55 boolean containsNonZeroUnsafe(Object value);
56
57 /**
58 * @return the number of distinct values
59 */
60 int size();
61
62 /**
63 *
64 * @return iff contains at least one value with non-zero occurrences
65 */
66 boolean isEmpty();
67
68 /**
69 * The set of distinct values
70 */
71 Set<T> distinctValues();
72
73
74 /**
75 * Where supported, returns the stored element that is equal to the given value, or null if none.
76 * Useful for canonicalization in case of non-identity equals().
77 *
78 * <p> For collections that do not support canonicalization, simply returns the argument if contained, null if none.
79 *
80 * @return a value equal to the argument if such a value is stored, or null if none
81 */
82 default T theContainedVersionOf(T value) {
83 if (containsNonZero(value)) return value; else return null;
84 }
85
86 /**
87 * Where supported, returns the stored element that is equal to the given value (of any type),
88 * or null if none.
89 * Useful for canonicalization in case of non-identity equals().
90 *
91 * <p> For collections that do not support canonicalization, simply returns the argument if contained, null if none.
92 *
93 * @return a value equal to the argument if such a value is stored, or null if none
94 */
95 @SuppressWarnings("unchecked")
96 default T theContainedVersionOfUnsafe(Object value) {
97 if (containsNonZeroUnsafe(value)) return (T) value; else return null;
98 }
99
100
101 /**
102 * @return an unmodifiable view of contained values with their multiplicities
103 */
104 default Iterable<Map.Entry<T, Integer>> entriesWithMultiplicities() {
105 return () -> {
106 Iterator<T> wrapped = distinctValues().iterator();
107 return new Iterator<Map.Entry<T, Integer>> () {
108 @Override
109 public boolean hasNext() {
110 return wrapped.hasNext();
111 }
112
113 @Override
114 public Map.Entry<T, Integer> next() {
115 T key = wrapped.next();
116 int count = getCount(key);
117 return new Map.Entry<T, Integer>(){
118 @Override
119 public T getKey() {
120 return key;
121 }
122
123 @Override
124 public Integer getValue() {
125 return count;
126 }
127
128 @Override
129 public Integer setValue(Integer value) {
130 throw new UnsupportedOperationException();
131 }
132
133 @Override
134 public String toString() {
135 return String.format("%d of %s", count, key);
136 }
137
138 };
139 }
140
141 };
142 };
143 }
144
145 /**
146 * Process contained values with their multiplicities
147 */
148 default void forEachEntryWithMultiplicities(BiConsumer<T, Integer> entryConsumer) {
149 for (T value : distinctValues()) {
150 entryConsumer.accept(value, getCount(value));
151 }
152 }
153
154
155 /**
156 * For compatibility with legacy code relying on element-to-integer maps.
157 * @return an unmodifiable view of contained values with their multiplicities
158 */
159 public default Map<T, Integer> asMap() {
160 return new MemoryViewBackedMapView<>(this);
161 }
162
163 /**
164 * For compatibility with legacy code relying on element-to-integer maps.
165 * @return an unmodifiable view of contained values with their multiplicities
166 */
167 public static <T> IMemoryView<T> fromMap(Map<T, Integer> wrapped) {
168 return new MapBackedMemoryView<>(wrapped);
169 }
170
171 /**
172 * @return a stream of values, iterable once
173 * @since 2.1
174 */
175 public default Stream<T> asStream() {
176 return StreamSupport.stream(spliterator(), false);
177 }
178
179 /**
180 * Provides semantic equality comparison.
181 */
182 public static <T> boolean equals(IMemoryView<T> self, Object obj) {
183 if (obj instanceof IMemoryView<?>) {
184 IMemoryView<?> other = (IMemoryView<?>) obj;
185 if (other.size() != self.size()) return false;
186 for (Entry<?, Integer> entry : other.entriesWithMultiplicities()) {
187 if ( !entry.getValue().equals(self.getCountUnsafe(entry.getKey())))
188 return false;
189 }
190 return true;
191 }
192 return false;
193 }
194
195 /**
196 * Provides semantic hashCode() comparison.
197 */
198 public static <T> int hashCode(IMemoryView<T> memory) {
199 int hashCode = 0;
200 for (T value : memory.distinctValues()) {
201 hashCode += value.hashCode() ^ Integer.hashCode(memory.getCount(value));
202 }
203 return hashCode;
204 }
205} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java
new file mode 100644
index 00000000..1ce1d2c9
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java
@@ -0,0 +1,216 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.stream.Stream;
12
13import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
14
15/**
16 * A multi-map that associates sets / multisets / delta sets of values to each key.
17 *
18 * <p> Implementors must provide semantic (not identity-based) hashCode() and equals() using the static helpers {@link #hashCode(IMultiLookup)} and {@link #equals(IMultiLookup, Object)} here.
19 *
20 * @author Gabor Bergmann
21 * @since 2.0
22 * @noimplement This interface is not intended to be implemented by clients.
23 */
24public interface IMultiLookup<Key, Value> {
25
26 /**
27 * Returns true if this collection is empty, false otherwise.
28 * @since 2.2
29 */
30 boolean isEmpty();
31
32
33 /**
34 * Returns true if there are any values associated with the given key.
35 * @param key a key for which associated values are sought
36 * @since 2.3
37 */
38 boolean lookupExists(Key key);
39
40 /**
41 * Returns a (read-only) bucket of values associated with the given key.
42 * Clients must not modify the returned bucket.
43 * @param key a key for which associated values are sought
44 * @return null if key not found, a bucket of values otherwise
45 */
46 IMemoryView<Value> lookup(Key key);
47
48 /**
49 * Returns a (read-only) bucket of values associated with the given key.
50 * Clients must not modify the returned bucket.
51 * @param key a key for which associated values are sought
52 * @return a bucket of values, never null
53 */
54 default IMemoryView<Value> lookupOrEmpty(Key key) {
55 IMemoryView<Value> bucket = lookup(key);
56 return bucket == null ? EmptyMemory.instance() : bucket;
57 }
58
59 /**
60 * Returns a (read-only) bucket of values associated with the given key, while simultaneously removing them.
61 * Clients must not modify the returned bucket.
62 * @param key a key for which associated values are sought
63 * @return a bucket of values, never null
64 * @since 2.3
65 */
66 IMemoryView<Value> lookupAndRemoveAll(Key key);
67
68 /**
69 * Returns a (read-only) bucket of values associated with the given key, which can be of any type.
70 * Clients must not modify the returned bucket.
71 * @param key a key for which associated values are sought (may or may not be of Key type)
72 * @return null if key not found, a bucket of values otherwise
73 */
74 IMemoryView<Value> lookupUnsafe(Object key);
75
76 /**
77 * Returns a (read-only) bucket of values associated with the given key.
78 * Clients must not modify the returned bucket.
79 * @param key a key for which associated values are sought (may or may not be of Key type)
80 * @return a bucket of values, never null
81 */
82 default IMemoryView<Value> lookupUnsafeOrEmpty(Object key) {
83 IMemoryView<Value> bucket = lookupUnsafe(key);
84 return bucket == null ? EmptyMemory.instance() : bucket;
85 }
86
87
88
89 /**
90 * @return the set of distinct keys that have values associated.
91 */
92 Iterable<Key> distinctKeys();
93
94 /**
95 * @return the set of distinct keys that have values associated.
96 * @since 2.3
97 */
98 Stream<Key> distinctKeysStream();
99
100 /**
101 * @return the number of distinct keys that have values associated.
102 */
103 int countKeys();
104
105 /**
106 * Iterates once over each distinct value.
107 */
108 Iterable<Value> distinctValues();
109
110 /**
111 * Iterates once over each distinct value.
112 * @since 2.3
113 */
114 Stream<Value> distinctValuesStream();
115
116
117
118 /**
119 * How significant was the change? *
120 * @author Gabor Bergmann
121 */
122 public enum ChangeGranularity {
123 /**
124 * First key-value pair with given key inserted, or last pair with given key deleted.
125 * (In case of delta maps, also if last negative key-value pair with given key neutralized.)
126 */
127 KEY,
128 /**
129 * First occurrence of given key-value pair inserted, or last occurrence of the pair deleted, while key still has values associated.
130 * (In case of delta maps, also if last negative occurrence of key-value pair neutralized.)
131 */
132 VALUE,
133 /**
134 * Duplicate key-value pair inserted or deleted.
135 */
136 DUPLICATE
137 }
138
139 /**
140 * Adds key-value pair to the lookup structure, or fails if not possible.
141 * <p> If the addition would cause duplicates but the bucket type does not allow it ({@link MemoryType#SETS}),
142 * the operation throws an {@link IllegalStateException}.
143 * @return the granularity of the change
144 * @throws IllegalStateException if addition would cause duplication that is not permitted
145 */
146 public ChangeGranularity addPair(Key key, Value value);
147 /**
148 * Adds key-value pair to the lookup structure.
149 * <p> If the addition would cause duplicates but the bucket type does not allow it ({@link MemoryType#SETS}),
150 * the operation is silently ignored and {@link ChangeGranularity#DUPLICATE} is returned.
151 * @return the granularity of the change, or {@link ChangeGranularity#DUPLICATE} if addition would result in a duplicate and therefore ignored
152 * @since 2.3
153 */
154 public ChangeGranularity addPairOrNop(Key key, Value value);
155 /**
156 * Removes key-value pair from the lookup structure, or fails if not possible.
157 * <p> When attempting to remove a key-value pair with zero multiplicity from a non-delta bucket type
158 * ({@link MemoryType#SETS} or {@link MemoryType#MULTISETS}}), an {@link IllegalStateException} is thrown.
159 * @return the granularity of the change
160 * @throws IllegalStateException if removing non-existing element that is not permitted
161 */
162 public ChangeGranularity removePair(Key key, Value value);
163 /**
164 * Removes key-value pair from the lookup structure.
165 * <p> When attempting to remove a key-value pair with zero multiplicity from a non-delta bucket type
166 * ({@link MemoryType#SETS} or {@link MemoryType#MULTISETS}}),
167 * the operation is silently ignored and {@link ChangeGranularity#DUPLICATE} is returned.
168 * @return the granularity of the change
169 * @throws IllegalStateException if removing non-existing element that is not permitted
170 * @since 2.3
171 */
172 public ChangeGranularity removePairOrNop(Key key, Value value);
173
174 /**
175 * Updates multiplicity of key-value pair by a positive amount.
176 *
177 * <p> PRE: count > 0
178 *
179 * @return the granularity of the change
180 * @throws IllegalStateException if addition would cause duplication that is not permitted
181 */
182 public ChangeGranularity addPairPositiveMultiplicity(Key key, Value value, int count);
183
184 /**
185 * Empties out the lookup structure.
186 */
187 public void clear();
188
189 /**
190 * Provides semantic equality comparison.
191 */
192 public static <Key, Value> boolean equals(IMultiLookup<Key, Value> self, Object obj) {
193 if (obj instanceof IMultiLookup<?, ?>) {
194 IMultiLookup<?, ?> other = (IMultiLookup<?, ?>) obj;
195 if (other.countKeys() != self.countKeys()) return false;
196 for (Object key : other.distinctKeys()) {
197 if (! other.lookupUnsafe(key).equals(self.lookupUnsafe(key)))
198 return false;
199 }
200 return true;
201 }
202 return false;
203 }
204
205 /**
206 * Provides semantic hashCode() comparison.
207 */
208 public static <Key, Value> int hashCode(IMultiLookup<Key, Value> memory) {
209 int hashCode = 0;
210 for (Key key : memory.distinctKeys()) {
211 hashCode += key.hashCode() ^ memory.lookup(key).hashCode();
212 }
213 return hashCode;
214 }
215
216}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java
new file mode 100644
index 00000000..8b1944c1
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java
@@ -0,0 +1,485 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collections;
12import java.util.Iterator;
13import java.util.NoSuchElementException;
14import java.util.Objects;
15import java.util.stream.Stream;
16import java.util.stream.StreamSupport;
17
18import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedSet;
19
20/**
21 * Specialized multimap implementation that saves memory
22 * by storing singleton value objects (multiplicity 1) instead of multiset buckets
23 * whenever there is only one value associated with a key.
24 *
25 * <p> See specialized {@link ToSetsAbstract}, {@link ToMultisetsAbstract} for various bucket types.
26 *
27 * <p> Implemented as a Key->Object map with invariant: <ul>
28 * <li> key maps to null if associated with no values;
29 * <li> key maps to a single Value iff it is associated with a single value of multiplicity +1;
30 * <li> key maps to Bucket otherwise
31 * </ul>
32 *
33 * Note that due to the above invariant, handling +1 and -1 are asymmetric in case of delta maps.
34 *
35 * <p> Not intended as an API, but rather as a 'base class' for implementors.
36 * Realized as an interface with default implementations, instead of an abstract class,
37 * to ensure that implementors can easily choose a base class such as UnifiedMap to augment.
38 *
39 * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible)
40 * and bind the lowLevel* methods accordingly.
41 *
42 * @noreference This interface is not intended to be referenced by clients.
43 * @noimplement This interface is not intended to be implemented by clients.
44 *
45 * @author Gabor Bergmann
46 * @since 2.0
47 *
48 *
49 */
50public interface IMultiLookupAbstract<Key, Value, Bucket extends MarkedMemory<Value>> extends IMultiLookup<Key, Value> {
51
52 // the following methods must be bound to a concrete Map<Key,Object>-like structure (primitive implementation allowed)
53
54 /**
55 * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map
56 */
57 abstract Object lowLevelGet(Key key);
58
59 /**
60 * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map
61 */
62 abstract Object lowLevelGetUnsafe(Object key);
63
64 /**
65 * Implementor shall bind to the low-level remove() or equivalent of the underlying Key-to-Object map
66 */
67 abstract Object lowLevelRemove(Key key);
68
69 /**
70 * Implementor shall bind to the low-level putIfAbsent() or equivalent of the underlying Key-to-Object map
71 */
72 abstract Object lowLevelPutIfAbsent(Key key, Value value);
73
74 /**
75 * Implementor shall bind to the low-level put() or equivalent of the underlying Key-to-Object map
76 */
77 abstract void lowLevelPut(Key key, Object valueOrBucket);
78
79 /**
80 * Implementor shall bind to the low-level values() or equivalent of the underlying Key-to-Object map
81 */
82 abstract Iterable<Object> lowLevelValues();
83
84 /**
85 * Implementor shall bind to the low-level keySet() or equivalent of the underlying Key-to-Object map
86 */
87 abstract Iterable<Key> lowLevelKeySet();
88
89 /**
90 * Implementor shall bind to the low-level size() or equivalent of the underlying Key-to-Object map
91 */
92 abstract int lowLevelSize();
93
94
95 // generic multi-lookup logic
96
97 @Override
98 default boolean lookupExists(Key key) {
99 Object object = lowLevelGet(key);
100 return null != object;
101 }
102
103 @Override
104 public default IMemoryView<Value> lookup(Key key) {
105 Object object = lowLevelGet(key);
106 if (object == null) return null;
107 if (object instanceof MarkedMemory) return (Bucket) object;
108 return yieldSingleton((Value)object);
109 }
110
111 @Override
112 default IMemoryView<Value> lookupAndRemoveAll(Key key) {
113 Object object = lowLevelRemove(key);
114 if (object == null) return EmptyMemory.instance();
115 if (object instanceof MarkedMemory) return (Bucket) object;
116 return yieldSingleton((Value)object);
117 }
118
119 @Override
120 public default IMemoryView<Value> lookupUnsafe(Object key) {
121 Object object = lowLevelGetUnsafe(key);
122 if (object == null) return null;
123 if (object instanceof MarkedMemory) return (Bucket) object;
124 return yieldSingleton((Value)object);
125 }
126
127 @Override
128 public default ChangeGranularity addPair(Key key, Value value) {
129 return addPairInternal(key, value, true);
130 }
131
132 @Override
133 default ChangeGranularity addPairOrNop(Key key, Value value) {
134 return addPairInternal(key, value, false);
135 }
136
137 public default ChangeGranularity addPairInternal(Key key, Value value, boolean throwIfImpossible) {
138 Object old = lowLevelPutIfAbsent(key, value);
139 boolean keyChange = (old == null);
140
141 if (keyChange) { // key was not present
142 return ChangeGranularity.KEY;
143 } else { // key was already present
144 Bucket bucket;
145 if (old instanceof MarkedMemory) { // ... as collection
146 bucket = (Bucket) old;
147 } else { // ... as singleton
148 if (!this.duplicatesAllowed() && Objects.equals(value, old)) {
149 if (throwIfImpossible)
150 throw new IllegalStateException();
151 else
152 return ChangeGranularity.DUPLICATE;
153 }
154 bucket = createSingletonBucket((Value) old);
155 lowLevelPut(key, bucket);
156 }
157 // will throw if forbidden duplicate, return false if allowed duplicate
158 if (addToBucket(bucket, value, throwIfImpossible)) {
159 // deltas may become empty or a singleton after addition!
160 if (negativesAllowed()) {
161 if (bucket.isEmpty()) {
162 lowLevelRemove(key);
163 return ChangeGranularity.KEY;
164 } else {
165 handleSingleton(key, bucket);
166 return ChangeGranularity.VALUE;
167 }
168 } else return ChangeGranularity.VALUE;
169 } else return ChangeGranularity.DUPLICATE;
170 }
171 }
172
173 @Override
174 // TODO deltas not supproted yet
175 default ChangeGranularity addPairPositiveMultiplicity(Key key, Value value, int count) {
176 if (count == 1) return addPair(key, value);
177 // count > 1, always end up with non-singleton bucket
178
179 Object old = lowLevelGet(key);
180 boolean keyChange = (old == null);
181
182 Bucket bucket;
183 if (keyChange) { // ... nothing associated to key yet
184 bucket = createSingletonBucket(value);
185 lowLevelPut(key, bucket);
186 --count; // one less to increment later
187 } else if (old instanceof MarkedMemory) { // ... as collection
188 bucket = (Bucket) old;
189 } else { // ... as singleton
190 bucket = createSingletonBucket((Value) old);
191 lowLevelPut(key, bucket);
192 }
193
194 boolean newValue = bucket.addSigned(value, count);
195
196 if (keyChange) return ChangeGranularity.KEY;
197 else if (newValue) return ChangeGranularity.VALUE;
198 else return ChangeGranularity.DUPLICATE;
199 }
200
201 @Override
202 public default ChangeGranularity removePair(Key key, Value value) {
203 return removePairInternal(key, value, true);
204 }
205
206 @Override
207 default ChangeGranularity removePairOrNop(Key key, Value value) {
208 return removePairInternal(key, value, false);
209 }
210
211 public default ChangeGranularity removePairInternal(Key key, Value value, boolean throwIfImpossible) {
212 Object old = lowLevelGet(key);
213 if (old instanceof MarkedMemory) { // ... as collection
214 @SuppressWarnings("unchecked")
215 Bucket bucket = (Bucket) old;
216 // will throw if removing non-existent, return false if removing duplicate
217 boolean valueChange = removeFromBucket(bucket, value, throwIfImpossible);
218 handleSingleton(key, bucket);
219 if (valueChange)
220 return ChangeGranularity.VALUE;
221 else
222 return ChangeGranularity.DUPLICATE;
223 } else if (value.equals(old)) { // matching singleton
224 lowLevelRemove(key);
225 return ChangeGranularity.KEY;
226 } else { // different singleton, will produce a delta if possible
227 if (negativesAllowed()) {
228 Bucket deltaBucket = createDeltaBucket((Value) old, value); // will throw if no deltas supported
229 lowLevelPut(key, deltaBucket);
230 return ChangeGranularity.VALUE; // no key change
231 } else {
232 if (throwIfImpossible)
233 throw new IllegalStateException();
234 else
235 return ChangeGranularity.DUPLICATE;
236 }
237 }
238 }
239
240 public default void handleSingleton(Key key, Bucket bucket) {
241 Value remainingSingleton = asSingleton(bucket);
242 if (remainingSingleton != null) { // only one remains
243 lowLevelPut(key, remainingSingleton);
244 }
245 }
246
247 @Override
248 public default Iterable<Value> distinctValues() {
249 return new Iterable<Value>() {
250 private final Iterator<Value> EMPTY_ITERATOR = Collections.<Value>emptySet().iterator();
251 @Override
252 public Iterator<Value> iterator() {
253 return new Iterator<Value>() {
254 Iterator<Object> bucketIterator = lowLevelValues().iterator();
255 Iterator<Value> elementIterator = EMPTY_ITERATOR;
256
257 @Override
258 public boolean hasNext() {
259 return (elementIterator.hasNext() || bucketIterator.hasNext());
260 }
261
262 @Override
263 public Value next() {
264 if (elementIterator.hasNext())
265 return elementIterator.next();
266 else if (bucketIterator.hasNext()) {
267 Object bucket = bucketIterator.next();
268 if (bucket instanceof MarkedMemory) {
269 elementIterator =
270 ((MarkedMemory) bucket).distinctValues().iterator();
271 return elementIterator.next();
272 } else {
273 elementIterator = EMPTY_ITERATOR;
274 return (Value) bucket;
275 }
276 } else
277 throw new NoSuchElementException();
278 }
279
280 /**
281 * Not implemented
282 */
283 @Override
284 public void remove() {
285 throw new UnsupportedOperationException();
286 }
287
288 };
289 }
290 };
291 }
292
293 @Override
294 default Stream<Value> distinctValuesStream() {
295 return StreamSupport.stream(distinctValues().spliterator(), false);
296 }
297
298 @Override
299 default Iterable<Key> distinctKeys() {
300 return lowLevelKeySet();
301 }
302
303 @Override
304 default Stream<Key> distinctKeysStream() {
305 return StreamSupport.stream(distinctKeys().spliterator(), false);
306 }
307
308 @Override
309 default int countKeys() {
310 return lowLevelSize();
311 }
312
313 // the following methods are customized for bucket type
314
315 /**
316 * @return iff negative multiplicites are allowed
317 */
318 abstract boolean negativesAllowed();
319
320 /**
321 * @return iff larger-than-1 multiplicites are allowed
322 * @since 2.3
323 */
324 abstract boolean duplicatesAllowed();
325
326 /**
327 * Increases the multiplicity of the value in the bucket.
328 * @return true iff non-duplicate
329 * @throws IllegalStateException if disallowed duplication and throwIfImpossible is specified
330 */
331 abstract boolean addToBucket(Bucket bucket, Value value, boolean throwIfImpossible);
332
333 /**
334 * Decreases the multiplicity of the value in the bucket.
335 * @return false if removing duplicate value
336 * @throws IllegalStateException if removing non-existing value (unless delta map) and throwIfImpossible is specified
337 */
338 abstract boolean removeFromBucket(Bucket bucket, Value value, boolean throwIfImpossible);
339
340 /**
341 * Checks whether the bucket is a singleton, i.e. it contains a single value with multiplicity +1
342 * @return the singleton value, or null if the bucket is not singleton
343 */
344 abstract Value asSingleton(Bucket bucket);
345
346 /**
347 * @return a new bucket consisting of a sole value
348 */
349 abstract Bucket createSingletonBucket(Value value);
350 /**
351 * @return a read-only bucket consisting of a sole value, to be returned to the user
352 */
353 default IMemoryView<Value> yieldSingleton(Value value) {
354 return new SingletonMemoryView<>(value);
355 }
356
357 /**
358 * @param positive the previously existing value, or null if the delta is to contain a single negative tuple
359 * @return a new bucket consisting of a delta of two values
360 * @throws IllegalStateException if deltas not supported
361 */
362 abstract Bucket createDeltaBucket(Value positive, Value negative);
363
364 /**
365 * A multi-lookup whose buckets are sets.
366 *
367 * <p> Not intended as an API, but rather as a 'base class' for implementors.
368 * Realized as an interface with default implementations, instead of an abstract class,
369 * to ensure that implementors can easily choose a base class such as UnifiedMap to augment.
370 *
371 * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible)
372 * and bind the lowLevel* methods accordingly.
373 *
374 * @noreference This interface is not intended to be referenced by clients.
375 * @noimplement This interface is not intended to be implemented by clients.
376 * @author Gabor Bergmann
377 */
378 public static interface ToSetsAbstract<Key, Value> extends IMultiLookupAbstract<Key, Value, MarkedMemory.MarkedSet<Value>> {
379 /**
380 * @return a fresh, empty marked set
381 */
382 public MarkedSet<Value> createMarkedSet();
383
384 @Override
385 public default boolean negativesAllowed() {
386 return false;
387 }
388 @Override
389 default boolean duplicatesAllowed() {
390 return false;
391 }
392
393 @Override
394 public default boolean addToBucket(MarkedSet<Value> bucket, Value value, boolean throwIfImpossible) {
395 if (bucket.addOne(value)) return true;
396 else if (throwIfImpossible) throw new IllegalStateException();
397 else return false;
398 }
399
400 @Override
401 public default boolean removeFromBucket(MarkedSet<Value> bucket, Value value, boolean throwIfImpossible) {
402 return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value);
403 }
404
405 @Override
406 public default Value asSingleton(MarkedSet<Value> bucket) {
407 return bucket.size() == 1 ? bucket.iterator().next() : null;
408 }
409
410 @Override
411 public default MarkedSet<Value> createSingletonBucket(Value value) {
412 MarkedSet<Value> result = createMarkedSet();
413 result.addOne(value);
414 return result;
415 }
416
417 @Override
418 public default MarkedSet<Value> createDeltaBucket(Value positive, Value negative) {
419 throw new IllegalStateException();
420 }
421 }
422
423 /**
424 * A multi-lookup whose buckets are multisets.
425 *
426 * <p> Not intended as an API, but rather as a 'base class' for implementors.
427 * Realized as an interface with default implementations, instead of an abstract class,
428 * to ensure that implementors can easily choose a base class such as UnifiedMap to augment.
429 *
430 * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible)
431 * and bind the lowLevel* methods accordingly.
432 *
433 * @noreference This interface is not intended to be referenced by clients.
434 * @noimplement This interface is not intended to be implemented by clients.
435 * @author Gabor Bergmann
436 */
437 public static interface ToMultisetsAbstract<Key, Value> extends IMultiLookupAbstract<Key, Value, MarkedMemory.MarkedMultiset<Value>> {
438 /**
439 * @return a fresh, empty marked multiset
440 */
441 public MarkedMemory.MarkedMultiset<Value> createMarkedMultiset();
442
443 @Override
444 public default boolean negativesAllowed() {
445 return false;
446 }
447 @Override
448 default boolean duplicatesAllowed() {
449 return true;
450 }
451
452 @Override
453 public default boolean addToBucket(MarkedMemory.MarkedMultiset<Value> bucket, Value value, boolean throwIfImpossible) {
454 return bucket.addOne(value);
455 }
456
457 @Override
458 public default boolean removeFromBucket(MarkedMemory.MarkedMultiset<Value> bucket, Value value, boolean throwIfImpossible) {
459 return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value);
460 }
461
462 @Override
463 public default Value asSingleton(MarkedMemory.MarkedMultiset<Value> bucket) {
464 if (bucket.size() != 1) return null;
465 Value candidate = bucket.iterator().next();
466 return bucket.getCount(candidate) == 1 ? candidate : null;
467 }
468
469 @Override
470 public default MarkedMemory.MarkedMultiset<Value> createSingletonBucket(Value value) {
471 MarkedMemory.MarkedMultiset<Value> result = createMarkedMultiset();
472 result.addOne(value);
473 return result;
474 }
475
476 @Override
477 public default MarkedMemory.MarkedMultiset<Value> createDeltaBucket(Value positive, Value negative) {
478 throw new IllegalStateException();
479 }
480 }
481
482
483 // TODO add ToDeltaBagsAbstract
484
485} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java
new file mode 100644
index 00000000..bdd5d597
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * An {@link IMemory} that always contains values with a nonnegative multiplicity.
13 *
14 * <p> In case a write operation caused underflow, an {@link IllegalStateException} is thrown.
15 *
16 * @author Gabor Bergmann
17 * @since 1.7
18 */
19public interface IMultiset<T> extends IMemory<T> {
20
21 /**
22 * Adds the given number of occurrences to the memory. The count value must be a positive number.
23 *
24 * @param count
25 * the number of occurrences
26 * @return true if the tuple was not present before in the memory
27 */
28 boolean addPositive(T value, int count);
29
30} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java
new file mode 100644
index 00000000..cd25dc95
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.function.Function;
12import java.util.function.Supplier;
13
14import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
15
16/**
17 * A provider interface useful in various registry instances.
18 *
19 * @author Zoltan Ujhelyi
20 *
21 */
22public interface IProvider<T> extends Supplier<T>{
23
24 public final class ProvidedValueFunction implements Function<IProvider<PQuery>, PQuery> {
25 @Override
26 public PQuery apply(IProvider<PQuery> input) {
27 return (input == null) ? null : input.get();
28 }
29 }
30}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java
new file mode 100644
index 00000000..0c03da48
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java
@@ -0,0 +1,37 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.function.BiConsumer;
12
13/**
14 * An {@link IMemory} that always contains values with a 0 or +1 multiplicity.
15 *
16 * <p> In case a write operation causes underflow or overflow, an {@link IllegalStateException} is thrown.
17 *
18 * @author Gabor Bergmann
19 * @since 2.0
20 */
21public interface ISetMemory<T> extends IMemory<T> {
22
23 @Override
24 default void forEachEntryWithMultiplicities(BiConsumer<T, Integer> entryConsumer) {
25 for (T t : this.distinctValues()) entryConsumer.accept(t, 1);
26 }
27
28
29 @Override
30 default boolean removeOne(T value) {
31 if (!removeOneOrNop(value))
32 throw new IllegalStateException();
33 return true;
34 }
35
36
37}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java
new file mode 100644
index 00000000..3be078bd
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Iterator;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Set;
15import java.util.function.BiConsumer;
16
17/**
18 * Wraps a Map<T, Integer> (mapping elements to non-zero multiplicities) into an {@link IMemoryView}.
19 *
20 * @author Gabor Bergmann
21 * @since 2.0
22 */
23public class MapBackedMemoryView<T> implements IMemoryView<T> {
24
25 private Map<T, Integer> wrapped;
26
27 /**
28 * @param wrapped an equivalent map from contained objects to multiplicities
29 */
30 protected MapBackedMemoryView(Map<T, Integer> wrapped) {
31 super();
32 this.wrapped = wrapped;
33 }
34
35 @Override
36 public Iterator<T> iterator() {
37 return wrapped.keySet().iterator();
38 }
39
40 @Override
41 public int getCount(T value) {
42 return getCountUnsafe(value);
43 }
44
45 @Override
46 public int getCountUnsafe(Object value) {
47 Integer count = wrapped.get(value);
48 return count == null ? 0 : count;
49 }
50
51 @Override
52 public boolean containsNonZero(T value) {
53 return wrapped.containsKey(value);
54 }
55
56 @Override
57 public boolean containsNonZeroUnsafe(Object value) {
58 return wrapped.containsKey(value);
59 }
60
61 @Override
62 public int size() {
63 return wrapped.size();
64 }
65
66 @Override
67 public boolean isEmpty() {
68 return wrapped.isEmpty();
69 }
70
71 @Override
72 public Set<T> distinctValues() {
73 return wrapped.keySet();
74 }
75
76
77 @Override
78 public void forEachEntryWithMultiplicities(BiConsumer<T, Integer> entryConsumer) {
79 for (Entry<T, Integer> entry : wrapped.entrySet()) {
80 entryConsumer.accept(entry.getKey(), entry.getValue());
81 }
82 }
83
84 @Override
85 public Iterable<Entry<T, Integer>> entriesWithMultiplicities() {
86 return wrapped.entrySet();
87 }
88
89 @Override
90 public int hashCode() {
91 return IMemoryView.hashCode(this);
92 }
93 @Override
94 public boolean equals(Object obj) {
95 return IMemoryView.equals(this, obj);
96 }
97
98 @Override
99 public String toString() {
100 return wrapped.toString();
101 }
102}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java
new file mode 100644
index 00000000..d22dcbe7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * Internal marker type, must only be instantiated inside implementors of IMultiLookupImpl
13 * @noimplement This interface is not intended to be implemented by clients.
14 * @since 2.0
15 */
16public interface MarkedMemory<Value> extends IMemory<Value> {
17
18 static interface MarkedSet<Value> extends MarkedMemory<Value> {}
19 static interface MarkedMultiset<Value> extends MarkedMemory<Value> {}
20 static interface MarkedDeltaBag<Value> extends MarkedMemory<Value> {}
21} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java
new file mode 100644
index 00000000..49711a89
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.Map;
15import java.util.Set;
16
17/**
18 * A partial and read-only Map implementation, mapping elements to multiplicities backed by an {@link IMemoryView}.
19 *
20 * <p> Not implemented: write methods.
21 *
22 * <p> Inefficiently implemented: {@link #containsValue(Object)}, {@link #values()}, {@link #entrySet()}.
23 *
24 * @author Gabor Bergmann
25 * @since 2.0
26 */
27public class MemoryViewBackedMapView<T> implements Map<T, Integer> {
28
29 private static final String READ_ONLY = "Read only";
30 private final IMemoryView<T> wrapped;
31
32 /**
33 * @param wrapped a memory view whose contents are to be exposed as an element-to-integer map.
34 */
35 protected MemoryViewBackedMapView(IMemoryView<T> wrapped) {
36 super();
37 this.wrapped = wrapped;
38 }
39
40 @Override
41 public int size() {
42 return wrapped.size();
43 }
44
45 @Override
46 public boolean isEmpty() {
47 return wrapped.isEmpty();
48 }
49
50 @Override
51 public boolean containsKey(Object key) {
52 return wrapped.containsNonZeroUnsafe(key);
53 }
54
55 @Override
56 public boolean containsValue(Object value) {
57 if (value instanceof Integer) {
58 for (Entry<T, Integer> entry : wrapped.entriesWithMultiplicities()) {
59 if (entry.getValue().equals(value)) return true;
60 }
61 }
62 return false;
63 }
64
65 @Override
66 public Integer put(T key, Integer value) {
67 throw new UnsupportedOperationException(READ_ONLY);
68 }
69
70 @Override
71 public Integer get(Object key) {
72 int count = wrapped.getCountUnsafe(key);
73 if (count == 0) return null; else return count;
74 }
75
76 @Override
77 public Integer remove(Object key) {
78 throw new UnsupportedOperationException(READ_ONLY);
79 }
80
81 @Override
82 public void putAll(Map<? extends T, ? extends Integer> m) {
83 throw new UnsupportedOperationException(READ_ONLY);
84 }
85
86 @Override
87 public void clear() {
88 throw new UnsupportedOperationException(READ_ONLY);
89 }
90
91 @Override
92 public Set<T> keySet() {
93 return wrapped.distinctValues();
94 }
95
96 @Override
97 public Collection<Integer> values() {
98 Collection<Integer> result = new ArrayList<>();
99 wrapped.forEachEntryWithMultiplicities((value, count) -> result.add(count));
100 return result;
101 }
102
103 @Override
104 public Set<Entry<T, Integer>> entrySet() {
105 Set<Entry<T, Integer>> result = new HashSet<>();
106 for (Entry<T, Integer> entry : wrapped.entriesWithMultiplicities()) {
107 result.add(entry);
108 }
109 return result;
110 }
111
112
113 @Override
114 public String toString() {
115 return wrapped.toString();
116 }
117}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java
new file mode 100644
index 00000000..e9e5e3a0
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java
@@ -0,0 +1,208 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.function.Supplier;
12
13/**
14 * This class was motivated by the similar Preconditions class from Guava to provide simple precondition checking
15 * functionality. However, as starting with version 2.0 the runtime of VIATRA Query should not depend on Guava, the
16 * relevant functionality of the Preconditions checking functionality will be implemented here.
17 *
18 * @author Zoltan Ujhelyi
19 * @since 2.0
20 *
21 */
22public final class Preconditions {
23
24 private Preconditions() {
25 /* Utility class constructor */ }
26
27 /**
28 * Ensures the truth of an expression involving one or more parameters to the calling method.
29 *
30 * @param expression
31 * a boolean expression
32 * @throws IllegalArgumentException
33 * if {@code expression} is false
34 */
35 public static void checkArgument(boolean expression) {
36 if (!expression) {
37 throw new IllegalArgumentException();
38 }
39 }
40
41 /**
42 * Ensures the truth of an expression involving one or more parameters to the calling method.
43 *
44 * @param expression
45 * a boolean expression
46 * @param errorMessage
47 * the exception message to use if the check fails
48 * @throws IllegalArgumentException
49 * if {@code expression} is false
50 */
51 public static void checkArgument(boolean expression, String errorMessage) {
52 if (!expression) {
53 throw new IllegalArgumentException(errorMessage);
54 }
55 }
56
57 /**
58 * Ensures the truth of an expression involving one or more parameters to the calling method.
59 *
60 * @param expression
61 * a boolean expression
62 * @param errorMessageTemplate
63 * a template for the exception message should the check fail using the Java Formatter syntax; the same
64 * as used by {@link String#format(String, Object...)}.
65 * @param errorMessageArgs
66 * the arguments to be substituted into the message template.
67 * @throws IllegalArgumentException
68 * if {@code expression} is false
69 * @throws NullPointerException
70 * if the check fails and either {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't
71 * let this happen)
72 */
73 public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
74 if (!expression) {
75 throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs));
76 }
77 }
78
79 /**
80 * Ensures the truth of an expression involving one or more parameters to the calling method.
81 *
82 * @param expression
83 * a boolean expression
84 * @param messageSupplier a supplier that is called to calculate the error message if necessary
85 * @throws IllegalArgumentException
86 * if {@code expression} is false
87 */
88 public static void checkArgument(boolean expression, Supplier<String> messageSupplier) {
89 if (!expression) {
90 throw new IllegalArgumentException(messageSupplier.get());
91 }
92 }
93
94 /**
95 * Ensures the truth of an expression involving one or more fields of a class.
96 *
97 * @param expression
98 * a boolean expression
99 * @throws IllegalStateException
100 * if {@code expression} is false
101 */
102 public static void checkState(boolean expression) {
103 if (!expression) {
104 throw new IllegalStateException();
105 }
106 }
107
108 /**
109 * Ensures the truth of an expression involving one or more fields of a class.
110 *
111 * @param expression
112 * a boolean expression
113 * @param errorMessage
114 * the exception message to use if the check fails
115 * @throws IllegalStateException
116 * if {@code expression} is false
117 */
118 public static void checkState(boolean expression, String errorMessage) {
119 if (!expression) {
120 throw new IllegalStateException(errorMessage);
121 }
122 }
123
124 /**
125 * Ensures the truth of an expression involving one or more fields of a class.
126 *
127 * @param expression
128 * a boolean expression
129 * @param errorMessageTemplate
130 * a template for the exception message should the check fail using the Java Formatter syntax; the same
131 * as used by {@link String#format(String, Object...)}.
132 * @param errorMessageArgs
133 * the arguments to be substituted into the message template.
134 * @throws IllegalStateException
135 * if {@code expression} is false
136 * @throws NullPointerException
137 * if the check fails and either {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't
138 * let this happen)
139 */
140 public static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
141 if (!expression) {
142 throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs));
143 }
144 }
145
146 /**
147 * Ensures the truth of an expression involving one or more fields of a class.
148 *
149 * @param expression
150 * a boolean expression
151 * @param messageSupplier a supplier that is called to calculate the error message if necessary
152 * @throws IllegalStateException
153 * if {@code expression} is false
154 */
155 public static void checkState(boolean expression, Supplier<String> messageSupplier) {
156 if (!expression) {
157 throw new IllegalStateException(messageSupplier.get());
158 }
159 }
160
161 /**
162 * Ensures that an index is appropriate for a list or array of given size.
163 *
164 * @param index
165 * @param size
166 * @throws IndexOutOfBoundsException
167 * if index is negative or is greater or equal to size
168 */
169 public static void checkElementIndex(int index, int size) {
170 if (index < 0 || index >= size) {
171 throw new IndexOutOfBoundsException();
172 }
173 }
174
175 /**
176 * Ensures that an index is appropriate for a list or array of given size.
177 *
178 * @param index
179 * @param size
180 * @param errorMessageTemplate
181 * a template for the exception message should the check fail using the Java Formatter syntax; the same
182 * as used by {@link String#format(String, Object...)}.
183 * @param errorMessageArgs
184 * the arguments to be substituted into the message template.
185 * @throws IndexOutOfBoundsException
186 * if index is negative or is greater or equal to size
187 */
188 public static void checkElementIndex(int index, int size, String errorMessageTemplate, Object... errorMessageArgs) {
189 if (index < 0 || index >= size) {
190 throw new IndexOutOfBoundsException(String.format(errorMessageTemplate, errorMessageArgs));
191 }
192 }
193
194 /**
195 * Ensures that an index is appropriate for a list or array of given size.
196 *
197 * @param index
198 * @param size
199 * @param messageSupplier a supplier that is called to calculate the error message if necessary
200 * @throws IndexOutOfBoundsException
201 * if index is negative or is greater or equal to size
202 */
203 public static void checkElementIndex(int index, int size, Supplier<String> messageSupplier) {
204 if (index < 0 || index >= size) {
205 throw new IndexOutOfBoundsException(messageSupplier.get());
206 }
207 }
208}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java
new file mode 100644
index 00000000..c4e6b5af
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java
@@ -0,0 +1,44 @@
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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.HashMap;
12import java.util.Map;
13import java.util.function.Supplier;
14
15/**
16 * @author Zoltan Ujhelyi
17 * @since 1.7
18 * @noreference This class is not intended to be referenced by clients.
19 */
20public class PurgableCache implements ICache {
21
22 Map<Object, Object> storage = new HashMap<>();
23
24 @Override
25 @SuppressWarnings("unchecked")
26 public <T> T getValue(Object key, Class<? extends T> clazz, Supplier<T> valueProvider) {
27 if (storage.containsKey(key)) {
28 Object value = storage.get(key);
29 Preconditions.checkState(clazz.isInstance(value), "Cache stores for key %s a value of %s that is incompatible with the requested type %s", key, value, clazz);
30 return (T) value;
31 } else {
32 T value = valueProvider.get();
33 storage.put(key, value);
34 return value;
35 }
36 }
37
38 /**
39 * Removes all values stored in the cache
40 */
41 public void purge() {
42 storage.clear();
43 }
44}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java
new file mode 100644
index 00000000..3749fe06
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java
@@ -0,0 +1,90 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *
9 * Contributors:
10 * Gabor Bergmann - initial API and implementation
11 *******************************************************************************/
12package tools.refinery.viatra.runtime.matchers.util;
13
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Set;
17import java.util.stream.Collectors;
18import java.util.stream.Stream;
19
20/**
21 * This class was motivated by the similar Sets class from Guava to provide simple set manipulation
22 * functionality. However, as starting with version 2.3 the runtime of VIATRA Query should not depend on Guava,
23 * not even internally, the relevant subset of Sets methods will be reimplemented here.
24 *
25 * <p> The current approach is to delegate to Eclipse Collections wherever possible.
26 * Such glue methods are useful so that downstream clients can avoid directly depending on Eclipse Collections.
27 *
28 * <p> Without an equivalent from Eclipse Collections, {@link #cartesianProduct(List)} is implemented here from scratch.
29 *
30 * @author Gabor Bergmann
31 * @since 2.3
32 */
33public final class Sets {
34
35 /**
36 * @since 2.4
37 */
38 public static <A> Set<A> newSet(Iterable<A> elements) {
39 return org.eclipse.collections.impl.factory.Sets.mutable.ofAll(elements);
40 }
41
42 public static <A> Set<A> intersection(Set<A> left, Set<A> right) {
43 return org.eclipse.collections.impl.factory.Sets.intersect(left, right);
44 }
45
46 public static <A> Set<A> difference(Set<A> left, Set<A> right) {
47 return org.eclipse.collections.impl.factory.Sets.difference(left, right);
48 }
49
50 public static <A> Set<A> union(Set<A> left, Set<A> right) {
51 return org.eclipse.collections.impl.factory.Sets.union(left, right);
52 }
53
54 public static <A> Set<? extends Set<A>> powerSet(Set<A> set) {
55 return org.eclipse.collections.impl.factory.Sets.powerSet(set);
56 }
57
58 public static <A> Set<List<A>> cartesianProduct(List<? extends Set<? extends A>> setsList) {
59
60 class Suffix { // simple immutable linked list
61 private A head;
62 private Suffix next;
63
64 public Suffix(A head, Suffix next) {
65 super();
66 this.head = head;
67 this.next = next;
68 }
69
70 public List<A> toList() {
71 ArrayList<A> result = new ArrayList<>();
72 for (Suffix cursor = this; cursor!=null; cursor = cursor.next)
73 result.add(cursor.head);
74 return result;
75 }
76 }
77
78 // build result lists from end to start, in the form of suffixes
79 Stream<Suffix> suffixes = Stream.of((Suffix) null /* empty suffix*/);
80 for (int i = setsList.size()-1; i>=0; --i) { // iterate sets in reverse order
81 Set<? extends A> set = setsList.get(i);
82 suffixes = suffixes.flatMap(suffix -> set.stream().map(newElement -> new Suffix(newElement, suffix)));
83 }
84
85
86 return suffixes.map(Suffix::toList).collect(Collectors.toSet());
87 }
88
89
90}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java
new file mode 100644
index 00000000..8f8bc228
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Objects;
12
13/**
14 * A piece of data associated with a direction.
15 *
16 * @author Tamas Szabo
17 * @since 2.4
18 */
19public class Signed<Payload extends Comparable<Payload>> {
20
21 private final Payload payload;
22 private final Direction direction;
23
24 public Signed(final Direction direction, final Payload payload) {
25 this.payload = payload;
26 this.direction = direction;
27 }
28
29 public Payload getPayload() {
30 return payload;
31 }
32
33 public Direction getDirection() {
34 return direction;
35 }
36
37 @Override
38 public int hashCode() {
39 return Objects.hash(direction, payload);
40 }
41
42 @Override
43 public boolean equals(final Object obj) {
44 if (this == obj) {
45 return true;
46 } else if (obj == null || this.getClass() != obj.getClass()) {
47 return false;
48 } else {
49 @SuppressWarnings("rawtypes")
50 final Signed other = (Signed) obj;
51 return direction == other.direction && Objects.equals(payload, other.payload);
52 }
53 }
54
55 @Override
56 public String toString() {
57 return this.direction.asSign() + this.payload.toString();
58 }
59
60} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java
new file mode 100644
index 00000000..cc5963f7
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2015, Zoltan Ujhelyi, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11/**
12 * A provider implementation that always returns the same object instance.
13 * @author Zoltan Ujhelyi
14 */
15public class SingletonInstanceProvider<T> implements IProvider<T>{
16
17 private T instance;
18
19 public SingletonInstanceProvider(T instance) {
20 Preconditions.checkArgument(instance != null, "Instance parameter must not be null.");
21 this.instance = instance;
22 }
23
24 @Override
25 public T get() {
26 return instance;
27 }
28
29} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java
new file mode 100644
index 00000000..b303f9ad
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2018, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collections;
12import java.util.Iterator;
13import java.util.NoSuchElementException;
14import java.util.Set;
15
16/**
17 * An immutable memory view that consists of a single non-null element with multiplicity 1.
18 * @author Gabor Bergmann
19 * @since 2.0
20 */
21public final class SingletonMemoryView<Value> implements IMemoryView<Value> {
22
23 private Value wrapped;
24 private static final int ONE_HASH = Integer.valueOf(1).hashCode();
25
26 public SingletonMemoryView(Value value) {
27 this.wrapped = value;
28 }
29
30 @Override
31 public Iterator<Value> iterator() {
32 return new Iterator<Value>() {
33 boolean hasNext = true;
34
35 @Override
36 public boolean hasNext() {
37 return hasNext;
38 }
39
40 @Override
41 public Value next() {
42 if (hasNext) {
43 hasNext = false;
44 return wrapped;
45 } else throw new NoSuchElementException();
46 }
47 };
48 }
49
50 @Override
51 public int getCount(Value value) {
52 return wrapped.equals(value) ? 1 : 0;
53 }
54
55 @Override
56 public int getCountUnsafe(Object value) {
57 return wrapped.equals(value) ? 1 : 0;
58 }
59
60 @Override
61 public boolean containsNonZero(Value value) {
62 return wrapped.equals(value);
63 }
64
65 @Override
66 public boolean containsNonZeroUnsafe(Object value) {
67 return wrapped.equals(value);
68 }
69
70 @Override
71 public int size() {
72 return 1;
73 }
74
75 @Override
76 public boolean isEmpty() {
77 return false;
78 }
79
80 @Override
81 public Set<Value> distinctValues() {
82 return Collections.singleton(wrapped);
83 }
84
85 @Override
86 public boolean equals(Object obj) {
87 if (obj instanceof IMemoryView<?>) {
88 IMemoryView<?> other = (IMemoryView<?>) obj;
89 if (1 != other.size()) return false;
90 if (1 != other.getCountUnsafe(wrapped)) return false;
91 return true;
92 }
93 return false;
94 }
95
96 @Override
97 public int hashCode() {
98 return wrapped.hashCode() ^ ONE_HASH;
99 }
100
101 @Override
102 public String toString() {
103 return "{" + wrapped + "}";
104 }
105}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java
new file mode 100644
index 00000000..90fcad4d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java
@@ -0,0 +1,517 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.NavigableMap;
15import java.util.Set;
16import java.util.TreeMap;
17
18import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.util.resumable.Resumable;
21import tools.refinery.viatra.runtime.matchers.util.resumable.UnmaskedResumable;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
25
26/**
27 * A timely memory implementation that incrementally maintains the {@link Timeline}s of tuples. The memory is capable of
28 * lazy folding (see {@link Resumable}).
29 *
30 * @author Tamas Szabo
31 * @since 2.3
32 */
33public class TimelyMemory<Timestamp extends Comparable<Timestamp>> implements Clearable, UnmaskedResumable<Timestamp> {
34
35 protected final Map<Tuple, TreeMap<Timestamp, CumulativeCounter>> counters;
36 protected final Map<Tuple, Timeline<Timestamp>> timelines;
37 public final TreeMap<Timestamp, Map<Tuple, FoldingState>> foldingState;
38 protected final Set<Tuple> presentAtInfinity;
39 protected final boolean isLazy;
40 protected final Diff<Timestamp> EMPTY_DIFF = new Diff<Timestamp>();
41
42 public TimelyMemory() {
43 this(false);
44 }
45
46 public TimelyMemory(final boolean isLazy) {
47 this.counters = CollectionsFactory.createMap();
48 this.timelines = CollectionsFactory.createMap();
49 this.presentAtInfinity = CollectionsFactory.createSet();
50 this.isLazy = isLazy;
51 if (isLazy) {
52 this.foldingState = CollectionsFactory.createTreeMap();
53 } else {
54 this.foldingState = null;
55 }
56 }
57
58 @Override
59 public Set<Tuple> getResumableTuples() {
60 if (this.foldingState == null || this.foldingState.isEmpty()) {
61 return Collections.emptySet();
62 } else {
63 return this.foldingState.firstEntry().getValue().keySet();
64 }
65 }
66
67 @Override
68 public Timestamp getResumableTimestamp() {
69 if (this.foldingState == null || this.foldingState.isEmpty()) {
70 return null;
71 } else {
72 return this.foldingState.firstKey();
73 }
74 }
75
76 /**
77 * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the
78 * two states will be merged together.
79 */
80 protected void addFoldingState(final Tuple tuple, final FoldingState state, final Timestamp timestamp) {
81 assert state.diff != 0;
82 final Map<Tuple, FoldingState> tupleMap = this.foldingState.computeIfAbsent(timestamp,
83 k -> CollectionsFactory.createMap());
84 tupleMap.compute(tuple, (k, v) -> {
85 return v == null ? state : v.merge(state);
86 });
87 }
88
89 @Override
90 public Map<Tuple, Diff<Timestamp>> resumeAt(final Timestamp timestamp) {
91 Timestamp current = this.getResumableTimestamp();
92 if (current == null) {
93 throw new IllegalStateException("There is othing to fold!");
94 } else if (current.compareTo(timestamp) != 0) {
95 // It can happen that already registered folding states end up having zero diffs,
96 // and we are instructed to continue folding at a timestamp that is higher
97 // than the lowest timestamp with a folding state.
98 // However, we only do garbage collection in doFoldingState, so now it is time to
99 // first clean up those states with zero diffs.
100 while (current != null && current.compareTo(timestamp) < 0) {
101 final Map<Tuple, FoldingState> tupleMap = this.foldingState.remove(current);
102 for (final Entry<Tuple, FoldingState> entry : tupleMap.entrySet()) {
103 final Tuple key = entry.getKey();
104 final FoldingState value = entry.getValue();
105 if (value.diff != 0) {
106 throw new IllegalStateException("Expected zero diff during garbage collection at " + current
107 + ", but the diff was " + value.diff + "!");
108 }
109 doFoldingStep(key, value, current);
110 }
111 current = this.getResumableTimestamp();
112 }
113 if (current == null || current.compareTo(timestamp) != 0) {
114 throw new IllegalStateException("Expected to continue folding at " + timestamp + "!");
115 }
116 }
117
118 final Map<Tuple, Diff<Timestamp>> diffMap = CollectionsFactory.createMap();
119 final Map<Tuple, FoldingState> tupleMap = this.foldingState.remove(timestamp);
120 for (final Entry<Tuple, FoldingState> entry : tupleMap.entrySet()) {
121 final Tuple key = entry.getKey();
122 final FoldingState value = entry.getValue();
123 diffMap.put(key, doFoldingStep(key, value, timestamp));
124 }
125
126 if (this.foldingState.get(timestamp) != null) {
127 throw new IllegalStateException(
128 "Folding at " + timestamp + " produced more folding work at the same timestamp!");
129 }
130
131 return diffMap;
132 }
133
134 protected Diff<Timestamp> doFoldingStep(final Tuple tuple, final FoldingState state, final Timestamp timestamp) {
135 final CumulativeCounter counter = getCounter(tuple, timestamp);
136 if (state.diff == 0) {
137 gcCounters(counter, tuple, timestamp);
138 return EMPTY_DIFF;
139 } else {
140 final Diff<Timestamp> resultDiff = new Diff<>();
141 final Timestamp nextTimestamp = this.counters.get(tuple).higherKey(timestamp);
142
143 final int oldCumulative = counter.cumulative;
144
145 counter.cumulative += state.diff;
146
147 computeDiffsLazy(state.diff < 0 ? Direction.DELETE : Direction.INSERT, oldCumulative, counter.cumulative,
148 timestamp, nextTimestamp, resultDiff);
149
150 gcCounters(counter, tuple, timestamp);
151 updateTimeline(tuple, resultDiff);
152
153 // prepare folding state for next timestamp
154 if (nextTimestamp != null) {
155 // propagate the incoming diff, not the diff stored in counter
156 addFoldingState(tuple, new FoldingState(state.diff), nextTimestamp);
157 }
158
159 return resultDiff;
160 }
161 }
162
163 /**
164 * On-demand initializes and returns the counter for the given tuple and timestamp.
165 */
166 protected CumulativeCounter getCounter(final Tuple tuple, final Timestamp timestamp) {
167 final TreeMap<Timestamp, CumulativeCounter> counterTimeline = this.counters.computeIfAbsent(tuple,
168 k -> CollectionsFactory.createTreeMap());
169
170 final CumulativeCounter counter = counterTimeline.computeIfAbsent(timestamp, k -> {
171 final Entry<Timestamp, CumulativeCounter> previousCounter = counterTimeline.lowerEntry(k);
172 final int previousCumulative = previousCounter == null ? 0 : previousCounter.getValue().cumulative;
173 return new CumulativeCounter(0, previousCumulative);
174 });
175
176 return counter;
177 }
178
179 /**
180 * Garbage collects the counter of the given tuple and timestamp if the new diff is zero.
181 */
182 protected void gcCounters(final CumulativeCounter counter, final Tuple tuple, final Timestamp timestamp) {
183 if (counter.diff == 0) {
184 final TreeMap<Timestamp, CumulativeCounter> counterMap = this.counters.get(tuple);
185 counterMap.remove(timestamp);
186 if (counterMap.isEmpty()) {
187 this.counters.remove(tuple);
188 }
189 }
190 }
191
192 /**
193 * Utility method that computes the timeline diffs in case of lazy memories. The diffs will be inserted into the
194 * input parameter. This method computes diffs for entire plateaus that spans from timestamp to nextTimestamp.
195 *
196 * Compared to the eager version of this method, the lazy version makes use of both the old and the new cumulative
197 * values because it can happen that the cumulative is incremented by a value that is larger than 1 (as folding
198 * states are merged together). This means that we cant decide whether the cumulative became positive by comparing
199 * the new value to 1.
200 */
201 protected void computeDiffsLazy(final Direction direction, final int oldCumulative, final int newCumulative,
202 final Timestamp timestamp, final Timestamp nextTimestamp, final Diff<Timestamp> diffs) {
203 if (direction == Direction.INSERT) {
204 if (newCumulative == 0) {
205 throw new IllegalStateException("Cumulative count can never be negative!");
206 } else {
207 if (oldCumulative == 0 /* current became positive */) {
208 // (1) either we sent out a DELETE before and now we need to cancel it,
209 // (2) or we just INSERT this for the first time
210 diffs.add(new Signed<>(Direction.INSERT, timestamp));
211 if (nextTimestamp != null) {
212 diffs.add(new Signed<>(Direction.DELETE, nextTimestamp));
213 }
214 } else /* current stays positive */ {
215 // nothing to do
216 }
217 }
218 } else {
219 if (newCumulative < 0) {
220 throw new IllegalStateException("Cumulative count can never be negative!");
221 } else {
222 if (newCumulative == 0 /* current became zero */) {
223 diffs.add(new Signed<>(Direction.DELETE, timestamp));
224 if (nextTimestamp != null) {
225 diffs.add(new Signed<>(Direction.INSERT, nextTimestamp));
226 }
227 } else /* current stays positive */ {
228 // nothing to do
229 }
230 }
231 }
232 }
233
234 /**
235 * Utility method that computes the timeline diffs in case of eager memories. The diffs will be inserted into the
236 * input parameter. This method computes diffs that describe momentary changes instead of plateaus. Returns a
237 * {@link SignChange} that describes how the sign has changed at the given timestamp.
238 */
239 protected SignChange computeDiffsEager(final Direction direction, final CumulativeCounter counter,
240 final SignChange signChangeAtPrevious, final Timestamp timestamp, final Diff<Timestamp> diffs) {
241 if (direction == Direction.INSERT) {
242 if (counter.cumulative == 0) {
243 throw new IllegalStateException("Cumulative count can never be negative!");
244 } else {
245 if (counter.cumulative == 1 /* current became positive */) {
246 if (signChangeAtPrevious != SignChange.BECAME_POSITIVE) {
247 // (1) either we sent out a DELETE before and now we need to cancel it,
248 // (2) or we just INSERT this for the first time
249 diffs.add(new Signed<>(Direction.INSERT, timestamp));
250 } else {
251 // we have already emitted this at the previous timestamp
252 // both previous and current became positive
253 throw new IllegalStateException(
254 "This would mean that the diff at current is 0 " + counter.diff);
255 }
256
257 // remember for next timestamp
258 return SignChange.BECAME_POSITIVE;
259 } else /* current stays positive */ {
260 if (signChangeAtPrevious == SignChange.BECAME_POSITIVE) {
261 // we sent out an INSERT before and now the timeline is positive already starting at previous
262 // we need to cancel the effect of this with a DELETE
263 diffs.add(new Signed<>(Direction.DELETE, timestamp));
264 } else {
265 // this is normal, both previous and current was positive and stays positive
266 }
267
268 // remember for next timestamp
269 return SignChange.IRRELEVANT;
270 }
271 }
272 } else {
273 if (counter.cumulative < 0) {
274 throw new IllegalStateException("Cumulative count can never be negative!");
275 } else {
276 if (counter.cumulative == 0 /* current became zero */) {
277 if (signChangeAtPrevious != SignChange.BECAME_ZERO) {
278 // (1) either we sent out a INSERT before and now we need to cancel it,
279 // (2) or we just DELETE this for the first time
280 diffs.add(new Signed<>(Direction.DELETE, timestamp));
281 } else {
282 // we have already emitted this at the previous timestamp
283 // both previous and current became zero
284 throw new IllegalStateException(
285 "This would mean that the diff at current is 0 " + counter.diff);
286 }
287
288 // remember for next timestamp
289 return SignChange.BECAME_ZERO;
290 } else /* current stays positive */ {
291 if (signChangeAtPrevious == SignChange.BECAME_ZERO) {
292 // we sent out a DELETE before and now the timeline is zero already starting at previous
293 // we need to cancel the effect of this with a INSERT
294 diffs.add(new Signed<>(Direction.INSERT, timestamp));
295 } else {
296 // this is normal, both previous and current was positive and stays positive
297 }
298
299 // remember for next timestamp
300 return SignChange.IRRELEVANT;
301 }
302 }
303 }
304 }
305
306 public Diff<Timestamp> put(final Tuple tuple, final Timestamp timestamp) {
307 if (this.isLazy) {
308 return putLazy(tuple, timestamp);
309 } else {
310 return putEager(tuple, timestamp);
311 }
312 }
313
314 public Diff<Timestamp> remove(final Tuple tuple, final Timestamp timestamp) {
315 if (this.isLazy) {
316 return removeLazy(tuple, timestamp);
317 } else {
318 return removeEager(tuple, timestamp);
319 }
320 }
321
322 protected Diff<Timestamp> putEager(final Tuple tuple, final Timestamp timestamp) {
323 final Diff<Timestamp> resultDiff = new Diff<>();
324 final CumulativeCounter counter = getCounter(tuple, timestamp);
325 ++counter.diff;
326
327 // before the INSERT timestamp, no change at all
328 // it cannot happen that those became positive in this round
329 SignChange signChangeAtPrevious = SignChange.IRRELEVANT;
330
331 final NavigableMap<Timestamp, CumulativeCounter> nextCounters = this.counters.get(tuple).tailMap(timestamp,
332 true);
333 for (final Entry<Timestamp, CumulativeCounter> currentEntry : nextCounters.entrySet()) {
334 final Timestamp currentTimestamp = currentEntry.getKey();
335 final CumulativeCounter currentCounter = currentEntry.getValue();
336 ++currentCounter.cumulative;
337 signChangeAtPrevious = computeDiffsEager(Direction.INSERT, currentCounter, signChangeAtPrevious,
338 currentTimestamp, resultDiff);
339 }
340
341 gcCounters(counter, tuple, timestamp);
342 updateTimeline(tuple, resultDiff);
343
344 return resultDiff;
345 }
346
347 protected Diff<Timestamp> putLazy(final Tuple tuple, final Timestamp timestamp) {
348 final CumulativeCounter counter = getCounter(tuple, timestamp);
349 counter.diff += 1;
350 // before the INSERT timestamp, no change at all
351 // it cannot happen that those became positive in this round
352 addFoldingState(tuple, new FoldingState(+1), timestamp);
353 return EMPTY_DIFF;
354 }
355
356 protected Diff<Timestamp> removeEager(final Tuple tuple, final Timestamp timestamp) {
357 final Diff<Timestamp> resultDiff = new Diff<>();
358 final CumulativeCounter counter = getCounter(tuple, timestamp);
359 --counter.diff;
360
361 // before the DELETE timestamp, no change at all
362 // it cannot happen that those became zero in this round
363 SignChange signChangeAtPrevious = SignChange.IRRELEVANT;
364
365 final NavigableMap<Timestamp, CumulativeCounter> nextCounters = this.counters.get(tuple).tailMap(timestamp,
366 true);
367 for (final Entry<Timestamp, CumulativeCounter> currentEntry : nextCounters.entrySet()) {
368 final Timestamp currentTimestamp = currentEntry.getKey();
369 final CumulativeCounter currentCounter = currentEntry.getValue();
370 --currentCounter.cumulative;
371 signChangeAtPrevious = computeDiffsEager(Direction.DELETE, currentCounter, signChangeAtPrevious,
372 currentTimestamp, resultDiff);
373 }
374
375 gcCounters(counter, tuple, timestamp);
376 updateTimeline(tuple, resultDiff);
377
378 return resultDiff;
379 }
380
381 protected Diff<Timestamp> removeLazy(final Tuple tuple, final Timestamp timestamp) {
382 final CumulativeCounter counter = getCounter(tuple, timestamp);
383 counter.diff -= 1;
384 // before the DELETE timestamp, no change at all
385 // it cannot happen that those became zero in this round
386 addFoldingState(tuple, new FoldingState(-1), timestamp);
387 return EMPTY_DIFF;
388 }
389
390 /**
391 * Updates and garbage collects the timeline of the given tuple based on the given timeline diff.
392 */
393 protected void updateTimeline(final Tuple tuple, final Diff<Timestamp> diff) {
394 if (!diff.isEmpty()) {
395 this.timelines.compute(tuple, (k, oldTimeline) -> {
396 this.presentAtInfinity.remove(tuple);
397 final Timeline<Timestamp> timeline = oldTimeline == null ? Timelines.createFrom(diff)
398 : oldTimeline.mergeAdditive(diff);
399 if (timeline.isPresentAtInfinity()) {
400 this.presentAtInfinity.add(tuple);
401 }
402 if (timeline.isEmpty()) {
403 return null;
404 } else {
405 return timeline;
406 }
407 });
408 }
409 }
410
411 /**
412 * @since 2.8
413 */
414 public Set<Tuple> getTuplesAtInfinity() {
415 return this.presentAtInfinity;
416 }
417
418 /**
419 * Returns the number of tuples that are present at the moment 'infinity'.
420 */
421 public int getCountAtInfinity() {
422 return this.presentAtInfinity.size();
423 }
424
425 /**
426 * Returns true if the given tuple is present at the moment 'infinity'.
427 */
428 public boolean isPresentAtInfinity(final Tuple tuple) {
429 final Timeline<Timestamp> timeline = this.timelines.get(tuple);
430 if (timeline == null) {
431 return false;
432 } else {
433 return timeline.isPresentAtInfinity();
434 }
435 }
436
437 public boolean isEmpty() {
438 return this.counters.isEmpty();
439 }
440
441 public int size() {
442 return this.counters.size();
443 }
444
445 public Set<Tuple> keySet() {
446 return this.counters.keySet();
447 }
448
449 public Map<Tuple, Timeline<Timestamp>> asMap() {
450 return this.timelines;
451 }
452
453 public Timeline<Timestamp> get(final ITuple tuple) {
454 return this.timelines.get(tuple);
455 }
456
457 @Override
458 public void clear() {
459 this.counters.clear();
460 this.timelines.clear();
461 if (this.foldingState != null) {
462 this.foldingState.clear();
463 }
464 }
465
466 public boolean containsKey(final ITuple tuple) {
467 return this.counters.containsKey(tuple);
468 }
469
470 @Override
471 public String toString() {
472 return this.counters + "\n" + this.timelines + "\n" + this.foldingState + "\n";
473 }
474
475 protected static final class CumulativeCounter {
476 protected int diff;
477 protected int cumulative;
478
479 protected CumulativeCounter(final int diff, final int cumulative) {
480 this.diff = diff;
481 this.cumulative = cumulative;
482 }
483
484 @Override
485 public String toString() {
486 return "{diff=" + this.diff + ", cumulative=" + this.cumulative + "}";
487 }
488
489 }
490
491 protected static final class FoldingState {
492 protected final int diff;
493
494 protected FoldingState(final int diff) {
495 this.diff = diff;
496 }
497
498 @Override
499 public String toString() {
500 return "{diff=" + this.diff + "}";
501 }
502
503 /**
504 * The returned result will never be null, even if the resulting diff is zero.
505 */
506 public FoldingState merge(final FoldingState that) {
507 Preconditions.checkArgument(that != null);
508 return new FoldingState(this.diff + that.diff);
509 }
510
511 }
512
513 protected enum SignChange {
514 BECAME_POSITIVE, BECAME_ZERO, IRRELEVANT;
515 }
516
517}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java
new file mode 100644
index 00000000..ea70e61d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.resumable;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
15
16/**
17 * A masked {@link Resumable} implementation, which maintains lazy folding per tuple signature.
18 *
19 * @author Tamas Szabo
20 * @since 2.4
21 */
22public interface MaskedResumable<Timestamp extends Comparable<Timestamp>> extends Resumable<Timestamp> {
23
24 /**
25 * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to
26 * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more
27 * folding to do towards higher timestamps.
28 */
29 public Map<Tuple, Map<Tuple, Diff<Timestamp>>> resumeAt(final Timestamp timestamp);
30
31 /**
32 * Returns the set of signatures for which lazy folding shall be resumed at the next timestamp.
33 */
34 public Iterable<Tuple> getResumableSignatures();
35
36}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java
new file mode 100644
index 00000000..2861df20
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.resumable;
10
11/**
12 * A resumable lazily folds its state towards higher timestamps. Folding shall be done in the increasing order of
13 * timestamps, and it shall be interrupted after each step. The resumable can then be instructed to resume the folding,
14 * one step at a time.
15 *
16 * @author Tamas Szabo
17 * @since 2.4
18 */
19public interface Resumable<Timestamp extends Comparable<Timestamp>> {
20
21 /**
22 * Returns the smallest timestamp where lazy folding shall be resumed, or null if there is no more folding to do in this
23 * resumable.
24 */
25 public Timestamp getResumableTimestamp();
26
27}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java
new file mode 100644
index 00000000..1671940b
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Tamas Szabo, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.resumable;
10
11import java.util.Map;
12
13import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
14import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
15
16/**
17 * A unmasked {@link Resumable} implementation, which maintains lazy folding without caring about tuple signatures.
18 *
19 * @author Tamas Szabo
20 * @since 2.4
21 */
22public interface UnmaskedResumable<Timestamp extends Comparable<Timestamp>> extends Resumable<Timestamp> {
23
24 /**
25 * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to
26 * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more
27 * folding to do towards higher timestamps.
28 */
29 public Map<Tuple, Diff<Timestamp>> resumeAt(final Timestamp timestamp);
30
31 /**
32 * Returns the set of tuples for which lazy folding shall be resumed at the next timestamp.
33 */
34 public Iterable<Tuple> getResumableTuples();
35
36}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java
new file mode 100644
index 00000000..0532d094
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java
@@ -0,0 +1,111 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.timeline;
10
11import java.util.ArrayList;
12import java.util.Iterator;
13import java.util.List;
14
15import tools.refinery.viatra.runtime.matchers.util.Direction;
16import tools.refinery.viatra.runtime.matchers.util.Signed;
17
18/**
19 * A compact timeline may cosist of an arbitrary amount of moments.
20 * It is backed by an {@link ArrayList}.
21 *
22 * @author Tamas Szabo
23 * @since 2.4
24 */
25public class CompactTimeline<Timestamp extends Comparable<Timestamp>> extends Timeline<Timestamp> {
26
27 protected final List<Timestamp> elements;
28
29 CompactTimeline() {
30 this.elements = new ArrayList<>();
31 }
32
33 CompactTimeline(final Timestamp timestamp) {
34 this();
35 this.elements.add(timestamp);
36 }
37
38 CompactTimeline(final List<Timestamp> timestamps) {
39 this.elements = new ArrayList<>(timestamps.size());
40 this.elements.addAll(timestamps);
41 }
42
43 CompactTimeline(final Diff<Timestamp> diff) {
44 this.elements = new ArrayList<>(diff.size());
45 Direction expected = Direction.INSERT;
46 for (Signed<Timestamp> signed : diff) {
47 if (!expected.equals(signed.getDirection())) {
48 throw new IllegalStateException(String.format("Expected direction (%s) constraint violated! %s @%s",
49 expected, diff, signed.getPayload()));
50 }
51 this.elements.add(signed.getPayload());
52 expected = expected.opposite();
53 }
54 }
55
56 @Override
57 public Signed<Timestamp> getSigned(final int index) {
58 final Direction direction = index % 2 == 0 ? Direction.INSERT : Direction.DELETE;
59 return new Signed<>(direction, this.getUnsigned(index));
60 }
61
62 @Override
63 public Timestamp getUnsigned(final int index) {
64 if (this.elements.size() <= index) {
65 throw new IllegalArgumentException(
66 "Timeline size (" + this.size() + ") is smaller than requested index " + index + "!");
67 } else {
68 return this.elements.get(index);
69 }
70 }
71
72 @Override
73 public int size() {
74 return this.elements.size();
75 }
76
77 @Override
78 public boolean isPresentAtInfinity() {
79 // if it has an odd length, then it ends with "INSERT"
80 return this.size() % 2 == 1;
81 }
82
83 @Override
84 public Iterable<Signed<Timestamp>> asChangeSequence() {
85 Iterable<Timestamp> outer = this.elements;
86 return () -> {
87 final Iterator<Timestamp> itr = outer.iterator();
88 return new Iterator<Signed<Timestamp>>() {
89 Direction direction = Direction.INSERT;
90
91 @Override
92 public boolean hasNext() {
93 return itr.hasNext();
94 }
95
96 @Override
97 public Signed<Timestamp> next() {
98 final Signed<Timestamp> result = new Signed<Timestamp>(direction, itr.next());
99 direction = direction.opposite();
100 return result;
101 }
102 };
103 };
104 }
105
106 @Override
107 public boolean isEmpty() {
108 return this.elements.isEmpty();
109 }
110
111}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java
new file mode 100644
index 00000000..cec6049e
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.timeline;
10
11import java.util.ArrayList;
12
13import tools.refinery.viatra.runtime.matchers.util.Signed;
14
15/**
16 * The description of a delta that specifies how a {@link Timeline} changes. It consists of {@link Signed} timestamps that
17 * depict the moments of insertions and deletions on the timeline.
18 *
19 * @author Tamas Szabo
20 * @since 2.4
21 * @param <Timestamp>
22 * the type representing the timestamps
23 */
24public class Diff<Timestamp extends Comparable<Timestamp>> extends ArrayList<Signed<Timestamp>> {
25
26 private static final long serialVersionUID = 3853460426655994160L;
27
28 public Diff() {
29
30 }
31
32 public void appendWithCancellation(Signed<Timestamp> item) {
33 if (this.isEmpty()) {
34 this.add(item);
35 } else {
36 final Signed<Timestamp> last = this.get(this.size() - 1);
37 final int lastMinusItem = last.getPayload().compareTo(item.getPayload());
38 if (lastMinusItem == 0) {
39 if (last.getDirection() != item.getDirection()) {
40 // cancellation
41 this.remove(this.size() - 1);
42 } else {
43 throw new IllegalStateException(
44 "Trying to insert or delete for the second time at the same timestamp! " + item);
45 }
46 } else if (lastMinusItem > 0) {
47 throw new IllegalStateException(
48 "Trying to append a timestamp that is smaller than the last one! " + last + " " + item);
49 } else {
50 this.add(item);
51 }
52 }
53 }
54
55} \ No newline at end of file
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java
new file mode 100644
index 00000000..526a95f5
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java
@@ -0,0 +1,73 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.timeline;
10
11import java.util.Collections;
12
13import tools.refinery.viatra.runtime.matchers.util.Direction;
14import tools.refinery.viatra.runtime.matchers.util.Signed;
15
16/**
17 * A timeline which solely consists of one timestamp value, representing a single insertion. Intuitively, a singleton
18 * timeline always represents a bump which starts at the given timestamp and lasts till plus infinity.
19 *
20 * @author Tamas Szabo
21 * @since 2.4
22 */
23public class SingletonTimeline<Timestamp extends Comparable<Timestamp>> extends Timeline<Timestamp> {
24
25 protected final Timestamp start;
26
27 SingletonTimeline(final Timestamp timestamp) {
28 this.start = timestamp;
29 }
30
31 SingletonTimeline(final Diff<Timestamp> diff) {
32 if (diff.size() != 1 || diff.get(0).getDirection() == Direction.DELETE) {
33 throw new IllegalArgumentException("There is only a single (insert) timestamp in the singleton timestamp!");
34 } else {
35 this.start = diff.get(0).getPayload();
36 }
37 }
38
39 @Override
40 public Signed<Timestamp> getSigned(final int index) {
41 return new Signed<>(Direction.INSERT, this.getUnsigned(index));
42 }
43
44 @Override
45 public Timestamp getUnsigned(final int index) {
46 if (index != 0) {
47 throw new IllegalArgumentException("There is only a single (insert) timestamp in the singleton timestamp!");
48 } else {
49 return this.start;
50 }
51 }
52
53 @Override
54 public int size() {
55 return 1;
56 }
57
58 @Override
59 public boolean isPresentAtInfinity() {
60 return true;
61 }
62
63 @Override
64 public Iterable<Signed<Timestamp>> asChangeSequence() {
65 return Collections.singletonList(this.getSigned(0));
66 }
67
68 @Override
69 public boolean isEmpty() {
70 return false;
71 }
72
73}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java
new file mode 100644
index 00000000..9214536c
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java
@@ -0,0 +1,146 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.timeline;
10
11import java.util.ArrayList;
12import java.util.Iterator;
13import java.util.List;
14
15import tools.refinery.viatra.runtime.matchers.util.Direction;
16import tools.refinery.viatra.runtime.matchers.util.Signed;
17
18/**
19 * A timeline describes the life cycle of a piece of data (typically a tuple in a relation) as a sequence of moments.
20 * Even moments represent appearances, odd moments represent disappearances. A timeline is immutable, once created, it
21 * is not possible to extend it with further moments.
22 *
23 * @author Tamas Szabo
24 * @since 2.4
25 */
26public abstract class Timeline<Timestamp extends Comparable<Timestamp>> {
27
28 public abstract Iterable<Signed<Timestamp>> asChangeSequence();
29
30 public abstract boolean isPresentAtInfinity();
31
32 public abstract boolean isEmpty();
33
34 public abstract int size();
35
36 public abstract Signed<Timestamp> getSigned(final int index);
37
38 public abstract Timestamp getUnsigned(final int index);
39
40 public Timeline<Timestamp> mergeMultiplicative(final Timeline<Timestamp> that) {
41 final List<Timestamp> result = new ArrayList<>();
42 int thisIdx = 0, thatIdx = 0;
43 Timestamp thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null;
44 Timestamp thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null;
45
46 while (thisNext != null || thatNext != null) {
47 int thisMinusThat = 0;
48 if (thisNext != null && thatNext != null) {
49 thisMinusThat = thisNext.compareTo(thatNext);
50 }
51 if (thisNext == null || thisMinusThat > 0) {
52 if (thisIdx % 2 == 1) {
53 result.add(thatNext);
54 }
55 thatIdx++;
56 thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null;
57 } else if (thatNext == null || thisMinusThat < 0) {
58 if (thatIdx % 2 == 1) {
59 result.add(thisNext);
60 }
61 thisIdx++;
62 thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null;
63 } else {
64 if (thisIdx % 2 == thatIdx % 2) {
65 result.add(thisNext);
66 }
67 thisIdx++;
68 thatIdx++;
69 thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null;
70 thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null;
71 }
72 }
73
74 return Timelines.createFrom(result);
75 }
76
77 /**
78 * Merges this timeline with the given timestamp diff. The expectation is that the resulting timeline starts with an
79 * insertion. The logic is similar to a merge sort; we iterate side-by-side over the timeline and the diff. During
80 * the merge, cancellation can happen if at the same timestamp we observe different signs at the corresponding
81 * timeline and diff elements.
82 */
83 public Timeline<Timestamp> mergeAdditive(final Diff<Timestamp> diff) {
84 final Iterator<Signed<Timestamp>> thisItr = this.asChangeSequence().iterator();
85 final Iterator<Signed<Timestamp>> diffItr = diff.iterator();
86 final List<Timestamp> result = new ArrayList<>();
87 Direction expected = Direction.INSERT;
88 Signed<Timestamp> thisNext = thisItr.hasNext() ? thisItr.next() : null;
89 Signed<Timestamp> diffNext = diffItr.hasNext() ? diffItr.next() : null;
90
91 while (thisNext != null || diffNext != null) {
92 int thisMinusDiff = 0;
93 if (thisNext != null && diffNext != null) {
94 thisMinusDiff = thisNext.getPayload().compareTo(diffNext.getPayload());
95 }
96
97 if (thisNext == null || thisMinusDiff > 0) {
98 if (!expected.equals(diffNext.getDirection())) {
99 throw new IllegalStateException(
100 String.format("Expected direction (%s) constraint violated! %s %s @%s", expected, this,
101 diff, diffNext.getPayload()));
102 }
103 result.add(diffNext.getPayload());
104 diffNext = diffItr.hasNext() ? diffItr.next() : null;
105 expected = expected.opposite();
106 } else if (diffNext == null || thisMinusDiff < 0) {
107 if (!expected.equals(thisNext.getDirection())) {
108 throw new IllegalStateException(
109 String.format("Expected direction (%s) constraint violated! %s %s @%s", expected, this,
110 diff, thisNext.getPayload()));
111 }
112 result.add(thisNext.getPayload());
113 thisNext = thisItr.hasNext() ? thisItr.next() : null;
114 expected = expected.opposite();
115 } else {
116 // they cancel out each other
117 if (diffNext.getDirection().equals(thisNext.getDirection())) {
118 throw new IllegalStateException(String.format("Changes do not cancel out each other! %s %s @%s",
119 this, diff, thisNext.getPayload()));
120 }
121 diffNext = diffItr.hasNext() ? diffItr.next() : null;
122 thisNext = thisItr.hasNext() ? thisItr.next() : null;
123 }
124 }
125
126 return Timelines.createFrom(result);
127 }
128
129 @Override
130 public String toString() {
131 final StringBuilder builder = new StringBuilder();
132 builder.append("[");
133 boolean first = true;
134 for (final Signed<Timestamp> element : this.asChangeSequence()) {
135 if (first) {
136 first = false;
137 } else {
138 builder.append(", ");
139 }
140 builder.append(element.toString());
141 }
142 builder.append("]");
143 return builder.toString();
144 }
145
146}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java
new file mode 100644
index 00000000..747fda15
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util.timeline;
10
11import java.util.List;
12
13/**
14 * Utility class for creating {@link Timeline}s.
15 * @author Tamas Szabo
16 * @since 2.4
17 */
18public final class Timelines<Timestamp extends Comparable<Timestamp>> {
19
20 public static <Timestamp extends Comparable<Timestamp>> Timeline<Timestamp> createEmpty() {
21 return new CompactTimeline<Timestamp>();
22 }
23
24 public static <Timestamp extends Comparable<Timestamp>> Timeline<Timestamp> createFrom(
25 final Diff<Timestamp> diffs) {
26 if (diffs.size() == 1) {
27 return new SingletonTimeline<Timestamp>(diffs);
28 } else {
29 return new CompactTimeline<Timestamp>(diffs);
30 }
31 }
32
33 public static <Timestamp extends Comparable<Timestamp>> Timeline<Timestamp> createFrom(
34 final List<Timestamp> timestamps) {
35 if (timestamps.size() == 1) {
36 return new SingletonTimeline<Timestamp>(timestamps.get(0));
37 } else {
38 return new CompactTimeline<Timestamp>(timestamps);
39 }
40 }
41
42 public static <Timestamp extends Comparable<Timestamp>> Timeline<Timestamp> createFrom(final Timestamp timestamp) {
43 return new SingletonTimeline<Timestamp>(timestamp);
44 }
45
46}
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java
new file mode 100644
index 00000000..229801f8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java
@@ -0,0 +1,72 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Bergmann Gabor, 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 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.util;
10
11import org.apache.log4j.Logger;
12import tools.refinery.viatra.runtime.matchers.util.Preconditions;
13
14/**
15 * Centralized logger of the VIATRA Query runtime.
16 * @author Bergmann Gabor
17 *
18 */
19public class ViatraQueryLoggingUtil {
20
21 private ViatraQueryLoggingUtil() {/*Utility class constructor*/}
22
23 private static Logger externalLogger;
24
25 public static void setExternalLogger(Logger externalLogger) {
26 Preconditions.checkArgument(externalLogger != null, "Must not set up null logger");
27 ViatraQueryLoggingUtil.externalLogger = externalLogger;
28 }
29 /**
30 * Provides a static default logger.
31 */
32 public static Logger getDefaultLogger() {
33 if (defaultRuntimeLogger == null) {
34 Logger parentLogger = externalLogger;
35 if (parentLogger == null) {
36 defaultRuntimeLogger = Logger.getLogger("org.eclipse.viatra");
37 } else {
38 defaultRuntimeLogger = Logger.getLogger(parentLogger.getName() + ".runtime");
39 }
40 if (defaultRuntimeLogger == null)
41 throw new AssertionError("Configuration error: unable to create default VIATRA Query runtime logger.");
42 }
43
44 return defaultRuntimeLogger;
45 }
46
47 private static String getLoggerClassname(Class<?> clazz) {
48 return clazz.getName().startsWith(getDefaultLogger().getName())
49 ? clazz.getName()
50 : getDefaultLogger().getName() + "." + clazz.getName();
51 }
52
53 /**
54 * Provides a class-specific logger that also stores the global logger settings of the VIATRA Query runtime
55 * @param clazz
56 */
57 public static Logger getLogger(Class<?> clazz) {
58 return Logger.getLogger(getLoggerClassname(clazz));
59 }
60
61 /**
62 * Provides a named logger that also stores the global logger settings of the VIATRA Query runtime
63 * @param clazz
64 * @param name a non-empty name to append to the class names
65 * @since 2.5
66 */
67 public static Logger getLogger(Class<?> clazz, String name) {
68 return Logger.getLogger(getLoggerClassname(clazz) + '.' + name);
69 }
70
71 private static Logger defaultRuntimeLogger;
72}