From 9adbb3d49899a87b3026c11cb4ba3ff77f4fb75b Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 19 Aug 2023 02:31:57 +0200 Subject: chore: import VIATRA source Make our modifications more maintainable by editing the source code directly instead of using reflection. --- subprojects/viatra-runtime-rete/about.html | 26 + subprojects/viatra-runtime-rete/build.gradle.kts | 15 + .../aggregation/AbstractColumnAggregatorNode.java | 474 +++++++++++ .../rete/aggregation/ColumnAggregatorNode.java | 369 ++++++++ .../viatra/runtime/rete/aggregation/CountNode.java | 38 + .../runtime/rete/aggregation/GroupedMap.java | 120 +++ .../runtime/rete/aggregation/GroupedSet.java | 114 +++ .../runtime/rete/aggregation/IAggregatorNode.java | 26 + .../aggregation/IndexerBasedAggregatorNode.java | 278 ++++++ ...FaithfulParallelTimelyColumnAggregatorNode.java | 217 +++++ ...ithfulSequentialTimelyColumnAggregatorNode.java | 280 ++++++ .../timely/FaithfulTimelyColumnAggregatorNode.java | 247 ++++++ ...irstOnlyParallelTimelyColumnAggregatorNode.java | 106 +++ ...stOnlySequentialTimelyColumnAggregatorNode.java | 117 +++ .../FirstOnlyTimelyColumnAggregatorNode.java | 212 +++++ .../runtime/rete/boundary/Disconnectable.java | 26 + .../rete/boundary/ExternalInputEnumeratorNode.java | 209 +++++ .../boundary/ExternalInputStatelessFilterNode.java | 68 ++ .../runtime/rete/boundary/InputConnector.java | 208 +++++ .../viatra/runtime/rete/boundary/ReteBoundary.java | 551 ++++++++++++ .../construction/RetePatternBuildException.java | 50 ++ .../basiclinear/BasicLinearLayout.java | 171 ++++ .../basiclinear/OrderingHeuristics.java | 90 ++ .../construction/plancompiler/CompilerHelper.java | 390 +++++++++ .../plancompiler/RecursionCutoffPoint.java | 86 ++ .../plancompiler/ReteRecipeCompiler.java | 948 +++++++++++++++++++++ .../rete/construction/quasitree/JoinCandidate.java | 162 ++++ .../quasitree/JoinOrderingHeuristics.java | 49 ++ .../construction/quasitree/QuasiTreeLayout.java | 205 +++++ .../rete/construction/quasitree/TieBreaker.java | 30 + .../runtime/rete/eval/AbstractEvaluatorNode.java | 65 ++ .../viatra/runtime/rete/eval/EvaluatorCore.java | 180 ++++ .../viatra/runtime/rete/eval/IEvaluatorNode.java | 25 + .../runtime/rete/eval/MemorylessEvaluatorNode.java | 75 ++ .../rete/eval/OutputCachingEvaluatorNode.java | 311 +++++++ .../runtime/rete/eval/RelationEvaluatorNode.java | 183 ++++ .../runtime/rete/index/DefaultIndexerListener.java | 28 + .../viatra/runtime/rete/index/DualInputNode.java | 348 ++++++++ .../viatra/runtime/rete/index/ExistenceNode.java | 199 +++++ .../rete/index/GenericProjectionIndexer.java | 76 ++ .../viatra/runtime/rete/index/IdentityIndexer.java | 76 ++ .../viatra/runtime/rete/index/Indexer.java | 71 ++ .../viatra/runtime/rete/index/IndexerListener.java | 41 + .../runtime/rete/index/IndexerWithMemory.java | 284 ++++++ .../viatra/runtime/rete/index/IterableIndexer.java | 34 + .../viatra/runtime/rete/index/JoinNode.java | 193 +++++ .../runtime/rete/index/MemoryIdentityIndexer.java | 55 ++ .../runtime/rete/index/MemoryNullIndexer.java | 54 ++ .../viatra/runtime/rete/index/NullIndexer.java | 88 ++ .../viatra/runtime/rete/index/OnetimeIndexer.java | 47 + .../runtime/rete/index/ProjectionIndexer.java | 21 + .../rete/index/SpecializedProjectionIndexer.java | 176 ++++ .../viatra/runtime/rete/index/StandardIndexer.java | 127 +++ .../rete/index/TransitiveClosureNodeIndexer.java | 121 +++ .../index/timely/TimelyMemoryIdentityIndexer.java | 51 ++ .../rete/index/timely/TimelyMemoryNullIndexer.java | 49 ++ .../rete/matcher/DRedReteBackendFactory.java | 49 ++ .../runtime/rete/matcher/HintConfigurator.java | 46 + .../rete/matcher/IncrementalMatcherCapability.java | 30 + .../runtime/rete/matcher/ReteBackendFactory.java | 100 +++ .../rete/matcher/ReteBackendFactoryProvider.java | 35 + .../viatra/runtime/rete/matcher/ReteEngine.java | 579 +++++++++++++ .../runtime/rete/matcher/RetePatternMatcher.java | 462 ++++++++++ .../runtime/rete/matcher/TimelyConfiguration.java | 61 ++ .../rete/matcher/TimelyReteBackendFactory.java | 64 ++ .../refinery/viatra/runtime/rete/misc/Bag.java | 43 + .../viatra/runtime/rete/misc/ConstantNode.java | 50 ++ .../runtime/rete/misc/DefaultDeltaMonitor.java | 43 + .../viatra/runtime/rete/misc/DeltaMonitor.java | 111 +++ .../viatra/runtime/rete/misc/SimpleReceiver.java | 109 +++ .../viatra/runtime/rete/network/BaseNode.java | 108 +++ .../runtime/rete/network/ConnectionFactory.java | 170 ++++ .../viatra/runtime/rete/network/IGroupable.java | 31 + .../viatra/runtime/rete/network/Network.java | 408 +++++++++ .../NetworkStructureChangeSensitiveNode.java | 30 + .../refinery/viatra/runtime/rete/network/Node.java | 62 ++ .../viatra/runtime/rete/network/NodeFactory.java | 375 ++++++++ .../runtime/rete/network/NodeProvisioner.java | 346 ++++++++ .../runtime/rete/network/PosetAwareReceiver.java | 39 + .../runtime/rete/network/ProductionNode.java | 28 + .../viatra/runtime/rete/network/Receiver.java | 85 ++ .../runtime/rete/network/RederivableNode.java | 34 + .../runtime/rete/network/ReinitializedNode.java | 14 + .../viatra/runtime/rete/network/ReteContainer.java | 729 ++++++++++++++++ .../viatra/runtime/rete/network/StandardNode.java | 121 +++ .../viatra/runtime/rete/network/Supplier.java | 82 ++ .../viatra/runtime/rete/network/Tunnel.java | 19 + .../viatra/runtime/rete/network/UpdateMessage.java | 31 + .../network/communication/CommunicationGroup.java | 103 +++ .../communication/CommunicationTracker.java | 467 ++++++++++ .../network/communication/MessageSelector.java | 19 + .../rete/network/communication/NodeComparator.java | 32 + .../rete/network/communication/PhasedSelector.java | 34 + .../rete/network/communication/Timestamp.java | 124 +++ .../timeless/RecursiveCommunicationGroup.java | 164 ++++ .../timeless/SingletonCommunicationGroup.java | 86 ++ .../timeless/TimelessCommunicationTracker.java | 149 ++++ .../communication/timely/ResumableNode.java | 36 + .../timely/TimelyCommunicationGroup.java | 171 ++++ .../timely/TimelyCommunicationTracker.java | 216 +++++ .../timely/TimelyIndexerListenerProxy.java | 81 ++ .../communication/timely/TimelyMailboxProxy.java | 102 +++ .../timely/TimestampTransformation.java | 48 ++ .../rete/network/delayed/DelayedCommand.java | 81 ++ .../network/delayed/DelayedConnectCommand.java | 27 + .../network/delayed/DelayedDisconnectCommand.java | 30 + .../network/indexer/DefaultMessageIndexer.java | 74 ++ .../network/indexer/GroupBasedMessageIndexer.java | 95 +++ .../rete/network/indexer/MessageIndexer.java | 33 + .../rete/network/mailbox/AdaptableMailbox.java | 32 + .../network/mailbox/FallThroughCapableMailbox.java | 30 + .../runtime/rete/network/mailbox/Mailbox.java | 78 ++ .../network/mailbox/MessageIndexerFactory.java | 23 + .../timeless/AbstractUpdateSplittingMailbox.java | 109 +++ .../mailbox/timeless/BehaviorChangingMailbox.java | 117 +++ .../network/mailbox/timeless/DefaultMailbox.java | 164 ++++ .../mailbox/timeless/PosetAwareMailbox.java | 218 +++++ .../mailbox/timeless/UpdateSplittingMailbox.java | 135 +++ .../rete/network/mailbox/timely/TimelyMailbox.java | 150 ++++ .../viatra/runtime/rete/remote/Address.java | 125 +++ .../viatra/runtime/rete/remote/RemoteReceiver.java | 63 ++ .../viatra/runtime/rete/remote/RemoteSupplier.java | 54 ++ .../single/AbstractUniquenessEnforcerNode.java | 138 +++ .../viatra/runtime/rete/single/CallbackNode.java | 37 + .../runtime/rete/single/DefaultProductionNode.java | 79 ++ .../rete/single/DiscriminatorBucketNode.java | 85 ++ .../rete/single/DiscriminatorDispatcherNode.java | 154 ++++ .../runtime/rete/single/EqualityFilterNode.java | 41 + .../viatra/runtime/rete/single/FilterNode.java | 69 ++ .../runtime/rete/single/InequalityFilterNode.java | 52 ++ .../rete/single/RepresentativeElectionNode.java | 125 +++ .../runtime/rete/single/SingleInputNode.java | 126 +++ .../runtime/rete/single/TimelyProductionNode.java | 63 ++ .../rete/single/TimelyUniquenessEnforcerNode.java | 161 ++++ .../runtime/rete/single/TransformerNode.java | 49 ++ .../runtime/rete/single/TransitiveClosureNode.java | 147 ++++ .../runtime/rete/single/TransparentNode.java | 48 ++ .../viatra/runtime/rete/single/TrimmerNode.java | 61 ++ .../rete/single/UniquenessEnforcerNode.java | 321 +++++++ .../runtime/rete/single/ValueBinderFilterNode.java | 44 + .../rete/traceability/ActiveNodeConflictTrace.java | 24 + .../runtime/rete/traceability/CompiledQuery.java | 54 ++ .../runtime/rete/traceability/CompiledSubPlan.java | 51 ++ .../traceability/ParameterProjectionTrace.java | 42 + .../rete/traceability/PatternTraceInfo.java | 17 + .../runtime/rete/traceability/PlanningTrace.java | 80 ++ .../runtime/rete/traceability/RecipeTraceInfo.java | 81 ++ .../runtime/rete/traceability/TraceInfo.java | 33 + .../rete/traceability/UserRequestTrace.java | 36 + .../runtime/rete/util/LexicographicComparator.java | 58 ++ .../refinery/viatra/runtime/rete/util/Options.java | 111 +++ .../runtime/rete/util/OrderingCompareAgent.java | 92 ++ .../viatra/runtime/rete/util/ReteHintOptions.java | 60 ++ 153 files changed, 19363 insertions(+) create mode 100644 subprojects/viatra-runtime-rete/about.html create mode 100644 subprojects/viatra-runtime-rete/build.gradle.kts create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java (limited to 'subprojects/viatra-runtime-rete') 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 @@ + + + + +About + + + +

About This Content

+ +

March 18, 2019

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply 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 +indicated 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 http://www.eclipse.org.

+ + 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 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + implementation(project(":refinery-viatra-runtime")) + implementation(project(":refinery-viatra-runtime-rete-recipes")) + implementation(libs.slf4j.log4j) +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * Groups incoming tuples by the given mask, and aggregates values at a specific index in each group. + *

+ * Direct children are not supported, use via outer join indexers instead. + *

+ * There are both timeless and timely implementations. + * + * @author Tamas Szabo + * @since 2.2 + * + */ +public abstract class AbstractColumnAggregatorNode extends SingleInputNode + implements Clearable, IAggregatorNode { + + /** + * @since 1.6 + */ + protected final IMultisetAggregationOperator operator; + + /** + * @since 1.6 + */ + protected final TupleMask groupMask; + + /** + * @since 1.6 + */ + protected final TupleMask columnMask; + + /** + * @since 1.6 + */ + protected final int sourceWidth; + + /** + * @since 1.6 + */ + protected final IQueryRuntimeContext runtimeContext; + + protected final AggregateResult NEUTRAL; + + protected AggregatorOuterIndexer aggregatorOuterIndexer; + + @SuppressWarnings("rawtypes") + protected AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers; + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param deleteRederiveEvaluation + * true if the node should run in DRED mode, false otherwise + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param columnMask + * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over + * @param posetComparator + * the poset comparator for the column, if known, otherwise it can be null + * @since 1.6 + */ + public AbstractColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer); + this.operator = operator; + this.groupMask = groupMask; + this.columnMask = columnMask; + this.sourceWidth = groupMask.indices.length; + this.runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext(); + this.NEUTRAL = operator.getAggregate(operator.createNeutral()); + reteContainer.registerClearable(this); + } + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param aggregatedColumn + * the index of the column that the aggregator node is aggregating over + */ + public AbstractColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth)); + } + + @Override + public CommunicationTracker getCommunicationTracker() { + return this.reteContainer.getCommunicationTracker(); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public void appendChild(Receiver receiver) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public Indexer getAggregatorOuterIndexer() { + if (aggregatorOuterIndexer == null) { + aggregatorOuterIndexer = new AggregatorOuterIndexer(); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer); + } + return aggregatorOuterIndexer; + } + + @Override + public Indexer getAggregatorOuterIdentityIndexer(final int resultPositionInSignature) { + if (aggregatorOuterIdentityIndexers == null) { + aggregatorOuterIdentityIndexers = new AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[sourceWidth + + 1]; + } + if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) { + aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer( + resultPositionInSignature); + this.getCommunicationTracker().registerDependency(this, + aggregatorOuterIdentityIndexers[resultPositionInSignature]); + } + return aggregatorOuterIdentityIndexers[resultPositionInSignature]; + } + + /** + * @since 2.4 + */ + public void propagateAggregateResultUpdate(final Tuple group, final AggregateResult oldValue, + final AggregateResult newValue, final Timestamp timestamp) { + if (!Objects.equals(oldValue, newValue)) { + propagate(Direction.DELETE, group, oldValue, timestamp); + propagate(Direction.INSERT, group, newValue, timestamp); + } + } + + /** + * @since 2.4 + */ + @SuppressWarnings("unchecked") + public void propagate(final Direction direction, final Tuple group, final AggregateResult value, + final Timestamp timestamp) { + final Tuple tuple = tupleFromAggregateResult(group, value); + + if (aggregatorOuterIndexer != null) { + aggregatorOuterIndexer.propagate(direction, tuple, group, timestamp); + } + if (aggregatorOuterIdentityIndexers != null) { + for (final AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers) { + if (aggregatorOuterIdentityIndexer != null) { + aggregatorOuterIdentityIndexer.propagate(direction, tuple, group, timestamp); + } + } + } + } + + public abstract Tuple getAggregateTuple(final Tuple key); + + /** + * @since 2.4 + */ + public abstract Map> getAggregateTupleTimeline(final Tuple key); + + public abstract AggregateResult getAggregateResult(final Tuple key); + + /** + * @since 2.4 + */ + public abstract Map> getAggregateResultTimeline(final Tuple key); + + protected Tuple tupleFromAggregateResult(final Tuple groupTuple, final AggregateResult aggregateResult) { + if (aggregateResult == null) { + return null; + } else { + return Tuples.staticArityLeftInheritanceTupleOf(groupTuple, runtimeContext.wrapElement(aggregateResult)); + } + } + + /** + * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original + * signature. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * + */ + protected class AggregatorOuterIndexer extends StandardIndexer { + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public AggregatorOuterIndexer() { + super(AbstractColumnAggregatorNode.this.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1)); + this.parent = AbstractColumnAggregatorNode.this; + this.logic = createLogic(); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public Collection get(final Tuple signature) { + return this.logic.get(signature); + } + + @Override + public Map> getTimeline(final Tuple signature) { + return this.logic.getTimeline(signature); + } + + /** + * @since 2.4 + */ + public void propagate(final Direction direction, final Tuple tuple, final Tuple group, + final Timestamp timestamp) { + if (tuple != null) { + propagate(direction, tuple, group, true, timestamp); + } + } + + @Override + public Node getActiveNode() { + return AbstractColumnAggregatorNode.this; + } + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return this.TIMELY; + } else { + return this.TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signature) { + final Tuple aggregateTuple = getAggregateTuple(signature); + if (aggregateTuple == null) { + return null; + } else { + return Collections.singleton(aggregateTuple); + } + } + + @Override + public Map> getTimeline(final Tuple signature) { + throw new UnsupportedOperationException(); + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + return TIMELESS.get(signatureWithResult); + } + + @Override + public Map> getTimeline(final Tuple signature) { + final Map> aggregateTuples = getAggregateTupleTimeline(signature); + if (aggregateTuples.isEmpty()) { + return null; + } else { + return aggregateTuples; + } + } + + }; + + } + + /** + * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for + * this index is the original 'group by' masked tuple, with the suspected result inserted at position + * resultPositionInSignature. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * + */ + protected class AggregatorOuterIdentityIndexer extends StandardIndexer { + + protected final int resultPositionInSignature; + protected final TupleMask pruneResult; + protected final TupleMask reorderMask; + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public AggregatorOuterIdentityIndexer(final int resultPositionInSignature) { + super(AbstractColumnAggregatorNode.this.reteContainer, + TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1)); + this.resultPositionInSignature = resultPositionInSignature; + this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1); + if (resultPositionInSignature == sourceWidth) { + this.reorderMask = null; + } else { + this.reorderMask = mask; + } + this.logic = createLogic(); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public Collection get(final Tuple signatureWithResult) { + return this.logic.get(signatureWithResult); + } + + /** + * @since 2.4 + */ + @Override + public Map> getTimeline(final Tuple signature) { + return this.logic.getTimeline(signature); + } + + /** + * @since 2.4 + */ + public void propagate(final Direction direction, final Tuple tuple, final Tuple group, + final Timestamp timestamp) { + if (tuple != null) { + propagate(direction, reorder(tuple), group, true, timestamp); + } + } + + private Tuple reorder(final Tuple signatureWithResult) { + Tuple transformed; + if (reorderMask == null) { + transformed = signatureWithResult; + } else { + transformed = reorderMask.transform(signatureWithResult); + } + return transformed; + } + + @Override + public Node getActiveNode() { + return this.parent; + } + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return this.TIMELY; + } else { + return this.TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + final Tuple prunedSignature = pruneResult.transform(signatureWithResult); + final AggregateResult result = getAggregateResult(prunedSignature); + if (result != null && Objects.equals(signatureWithResult.get(resultPositionInSignature), result)) { + return Collections.singleton(signatureWithResult); + } else { + return null; + } + } + + @Override + public Map> getTimeline(final Tuple signature) { + throw new UnsupportedOperationException(); + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + return TIMELESS.get(signatureWithResult); + } + + @Override + public Map> getTimeline(final Tuple signatureWithResult) { + final Tuple prunedSignature = pruneResult.transform(signatureWithResult); + final Map> result = getAggregateResultTimeline(prunedSignature); + for (final Entry> entry : result.entrySet()) { + if (Objects.equals(signatureWithResult.get(resultPositionInSignature), entry.getKey())) { + return Collections.singletonMap(signatureWithResult, entry.getValue()); + } + } + return null; + } + + }; + + } + + /** + * @since 2.4 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + public abstract Collection get(final Tuple signatureWithResult); + + public abstract Map> getTimeline(final Tuple signature); + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox; + +/** + * Timeless implementation of the column aggregator node. + *

+ * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the + * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached + * during the evaluation. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @since 1.4 + */ +public class ColumnAggregatorNode + extends AbstractColumnAggregatorNode + implements RederivableNode, PosetAwareReceiver { + + /** + * @since 1.6 + */ + protected final IPosetComparator posetComparator; + + /** + * @since 1.6 + */ + protected final boolean deleteRederiveEvaluation; + + // invariant: neutral values are not stored + /** + * @since 1.6 + */ + protected final Map memory; + /** + * @since 1.6 + */ + protected final Map rederivableMemory; + + /** + * @since 1.7 + */ + protected CommunicationGroup currentGroup; + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param deleteRederiveEvaluation + * true if the node should run in DRED mode, false otherwise + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param columnMask + * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over + * @param posetComparator + * the poset comparator for the column, if known, otherwise it can be null + * @since 1.6 + */ + public ColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final boolean deleteRederiveEvaluation, final TupleMask groupMask, final TupleMask columnMask, + final IPosetComparator posetComparator) { + super(reteContainer, operator, groupMask, columnMask); + this.memory = CollectionsFactory.createMap(); + this.rederivableMemory = CollectionsFactory.createMap(); + this.deleteRederiveEvaluation = deleteRederiveEvaluation; + this.posetComparator = posetComparator; + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param aggregatedColumn + * the index of the column that the aggregator node is aggregating over + */ + public ColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, false, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth), + null); + } + + @Override + public boolean isInDRedMode() { + return this.deleteRederiveEvaluation; + } + + @Override + protected Mailbox instantiateMailbox() { + if (groupMask != null && columnMask != null && posetComparator != null) { + return new PosetAwareMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public TupleMask getCoreMask() { + return groupMask; + } + + @Override + public TupleMask getPosetMask() { + return columnMask; + } + + @Override + public IPosetComparator getPosetComparator() { + return posetComparator; + } + + @Override + public void rederiveOne() { + final Entry entry = rederivableMemory.entrySet().iterator().next(); + final Tuple group = entry.getKey(); + final Accumulator accumulator = entry.getValue(); + rederivableMemory.remove(group); + memory.put(group, accumulator); + // unregister the node if there is nothing left to be re-derived + if (this.rederivableMemory.isEmpty()) { + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + final AggregateResult value = operator.getAggregate(accumulator); + propagateAggregateResultUpdate(group, NEUTRAL, value, Timestamp.ZERO); + } + + @Override + public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) { + if (this.deleteRederiveEvaluation) { + updateWithDeleteAndRederive(direction, update, monotone); + } else { + updateDefault(direction, update, Timestamp.ZERO); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + updateWithPosetInfo(direction, update, false); + } + + /** + * @since 2.4 + */ + protected void updateDefault(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple key = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final Accumulator oldMainAccumulator = getMainAccumulator(key); + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, isInsertion); + storeIfNotNeutral(key, newMainAccumulator, memory); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + + propagateAggregateResultUpdate(key, oldValue, newValue, timestamp); + } + + /** + * @since 2.4 + */ + protected void updateWithDeleteAndRederive(final Direction direction, final Tuple update, final boolean monotone) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + Accumulator oldMainAccumulator = memory.get(group); + Accumulator oldRederivableAccumulator = rederivableMemory.get(group); + + if (direction == Direction.INSERT) { + // INSERT + if (oldRederivableAccumulator != null) { + // the group is in the re-derivable memory + final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator, + aggregableValue, isInsertion); + storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the accumulator became neutral in response to the INSERT + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the group is in the main memory + // at this point, it can happen that we need to initialize with a neutral accumulator + if (oldMainAccumulator == null) { + oldMainAccumulator = operator.createNeutral(); + } + + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, + isInsertion); + storeIfNotNeutral(group, newMainAccumulator, memory); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO); + } + } else { + // DELETE + if (oldRederivableAccumulator != null) { + // the group is in the re-derivable memory + if (oldMainAccumulator != null) { + issueError("[INTERNAL ERROR] Inconsistent state for " + update + + " because it is present both in the main and re-derivable memory in the ColumnAggregatorNode " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null); + } + try { + final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator, + aggregableValue, isInsertion); + storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the accumulator became neutral in response to the DELETE + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } catch (final NullPointerException ex) { + issueError("[INTERNAL ERROR] Deleting a domain element in " + update + + " which did not exist before in ColumnAggregatorNode " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), ex); + } + } else { + // the group is in the main memory + // at this point, it can happen that we need to initialize with a neutral accumulator + if (oldMainAccumulator == null) { + oldMainAccumulator = operator.createNeutral(); + } + + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, + isInsertion); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + + if (monotone) { + storeIfNotNeutral(group, newMainAccumulator, memory); + propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO); + } else { + final boolean wasEmpty = rederivableMemory.isEmpty(); + if (storeIfNotNeutral(group, newMainAccumulator, rederivableMemory) && wasEmpty) { + ((RecursiveCommunicationGroup) currentGroup).addRederivable(this); + } + memory.remove(group); + propagateAggregateResultUpdate(group, oldValue, NEUTRAL, Timestamp.ZERO); + } + } + } + } + + @Override + public void clear() { + this.memory.clear(); + this.rederivableMemory.clear(); + this.childMailboxes.clear(); + } + + /** + * Returns true if the accumulator was stored, false otherwise. + * + * @since 1.6 + */ + protected boolean storeIfNotNeutral(final Tuple key, final Accumulator accumulator, + final Map memory) { + if (operator.isNeutral(accumulator)) { + memory.remove(key); + return false; + } else { + memory.put(key, accumulator); + return true; + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + final Accumulator accumulator = getMainAccumulator(group); + final AggregateResult result = operator.getAggregate(accumulator); + return tupleFromAggregateResult(group, result); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final Accumulator accumulator = getMainAccumulator(group); + return operator.getAggregate(accumulator); + } + + @Override + public Map> getAggregateResultTimeline(Tuple key) { + throw new UnsupportedOperationException(); + } + + @Override + public Map> getAggregateTupleTimeline(Tuple key) { + throw new UnsupportedOperationException(); + } + + /** + * @since 1.6 + */ + protected Accumulator getMainAccumulator(final Tuple key) { + return getAccumulator(key, memory); + } + + /** + * @since 1.6 + */ + protected Accumulator getRederivableAccumulator(final Tuple key) { + return getAccumulator(key, rederivableMemory); + } + + /** + * @since 1.6 + */ + protected Accumulator getAccumulator(final Tuple key, final Map memory) { + Accumulator accumulator = memory.get(key); + if (accumulator == null) { + return operator.createNeutral(); + } else { + return accumulator; + } + } + + @Override + public CommunicationGroup getCurrentGroup() { + return currentGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.currentGroup = currentGroup; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * An aggregation node that simply counts the number of tuples conforming to the signature. + * + * @author Gabor Bergmann + * @since 1.4 + */ +public class CountNode extends IndexerBasedAggregatorNode { + + public CountNode(ReteContainer reteContainer) { + super(reteContainer); + } + + int sizeOf(Collection group) { + return group == null ? 0 : group.size(); + } + + @Override + public Object aggregateGroup(Tuple signature, Collection group) { + return sizeOf(group); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * An optimized {@link Map} implementation where each key is produced by joining together a group tuple and some other + * object (via left inheritance). Only a select few {@link Map} operations are supported. This collection is + * unmodifiable. + * + * Operations on this map assume that client queries also obey the contract that keys are constructed from a group tuple + * and an additional object. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class GroupedMap implements Map { + + protected final Tuple group; + // cached group size value is to be used in get() + private final int groupSize; + protected final Map mappings; + protected final IQueryRuntimeContext runtimeContext; + + public GroupedMap(final Tuple group, final Map mappings, + final IQueryRuntimeContext runtimeContext) { + this.group = group; + this.groupSize = group.getSize(); + this.mappings = mappings; + this.runtimeContext = runtimeContext; + } + + @Override + public int size() { + return this.mappings.size(); + } + + @Override + public boolean isEmpty() { + return this.mappings.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(final Object value) { + return this.mappings.containsValue(value); + } + + @Override + public ValueType get(final Object key) { + if (key instanceof Tuple) { + final Object value = ((Tuple) key).get(this.groupSize); + final Object unwrappedValue = this.runtimeContext.unwrapElement(value); + return this.mappings.get(unwrappedValue); + } else { + return null; + } + } + + @Override + public ValueType put(final Tuple key, final ValueType value) { + throw new UnsupportedOperationException(); + } + + @Override + public ValueType remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return new GroupedSet(this.group, this.mappings.keySet(), (g, v) -> { + return Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v)); + }); + } + + @Override + public Collection values() { + return this.mappings.values(); + } + + @Override + public Set> entrySet() { + return new GroupedSet>(this.group, this.mappings.keySet(), + (g, v) -> { + final Tuple key = Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v)); + final ValueType value = this.mappings.get(v); + return new SimpleEntry(key, value); + }); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiFunction; + +/** + * An optimized {@link Set} implementation where each contained value is produced by combining together a grouping value + * and some other (key) object. The way of combining together these two values is specified by the closure passed to the + * constructor. Only a select few {@link Set} operations are supported. This collection is unmodifiable. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class GroupedSet implements Set { + + protected final GroupingValueType group; + protected final Collection values; + protected final BiFunction valueFunc; + + public GroupedSet(final GroupingValueType group, final Collection values, + final BiFunction valueFunc) { + this.group = group; + this.values = values; + this.valueFunc = valueFunc; + } + + @Override + public int size() { + return this.values.size(); + } + + @Override + public boolean isEmpty() { + return this.values.isEmpty(); + } + + @Override + public boolean contains(final Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + final Iterator wrapped = this.values.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public WholeKeyType next() { + final GroupedKeyType value = wrapped.next(); + return valueFunc.apply(group, value); + } + }; + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(final T[] arr) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(final WholeKeyType tuple) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import tools.refinery.viatra.runtime.rete.index.Indexer; + +/** + * Expresses that aggregators expose specialized non-enumerable indexers for outer joining. + * @author Gabor Bergmann + * + * @since 1.4 + * + */ +public interface IAggregatorNode { + + Indexer getAggregatorOuterIndexer(); + + Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature); + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.DefaultIndexerListener; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * A special node depending on a projection indexer to aggregate tuple groups with the same projection. Only propagates + * the aggregates of non-empty groups. Use the outer indexers to circumvent. + *

+ * This node cannot be used in recursive differential dataflow evaluation. + * + * @author Gabor Bergmann + * @since 1.4 + */ +public abstract class IndexerBasedAggregatorNode extends StandardNode implements IAggregatorNode { + + ProjectionIndexer projection; + IndexerBasedAggregatorNode me; + int sourceWidth; + Map mainAggregates; + + AggregatorOuterIndexer aggregatorOuterIndexer = null; + AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers = null; + + /** + * MUST call initializeWith() afterwards! + */ + public IndexerBasedAggregatorNode(ReteContainer reteContainer) { + super(reteContainer); + this.me = this; + mainAggregates = CollectionsFactory.createMap(); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + /** + * @param projection + * the projection indexer whose tuple groups should be aggregated + */ + public void initializeWith(ProjectionIndexer projection) { + this.projection = projection; + this.sourceWidth = projection.getMask().indices.length; + + for (Tuple signature : projection.getSignatures()) { + mainAggregates.put(signature, aggregateGroup(signature, projection.get(signature))); + } + projection.attachListener(new DefaultIndexerListener(this) { + @Override + public void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) { + aggregateUpdate(direction, updateElement, signature, change); + } + }); + } + + /** + * Aggregates (reduces) a group of tuples. The group can be null. + */ + public abstract Object aggregateGroup(Tuple signature, Collection group); + + + /** + * Aggregates (reduces) a group of tuples, having access to the previous aggregated value (before the update) and + * the update definition. Defaults to aggregateGroup(). Override to increase performance. + * @since 2.4 + */ + public Object aggregateGroupAfterUpdate(Tuple signature, Collection currentGroup, Object oldAggregate, + Direction direction, Tuple updateElement, boolean change) { + return aggregateGroup(signature, currentGroup); + } + + protected Tuple aggregateAndPack(Tuple signature, Collection group) { + return packResult(signature, aggregateGroup(signature, group)); + } + + @Override + public Indexer getAggregatorOuterIndexer() { + if (aggregatorOuterIndexer == null) { + aggregatorOuterIndexer = new AggregatorOuterIndexer(); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer); + // reteContainer.connectAndSynchronize(this, aggregatorOuterIndexer); + } + return aggregatorOuterIndexer; + } + + @Override + public Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature) { + if (aggregatorOuterIdentityIndexers == null) + aggregatorOuterIdentityIndexers = new AggregatorOuterIdentityIndexer[sourceWidth + 1]; + if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) { + aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer( + resultPositionInSignature); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]); + // reteContainer.connectAndSynchronize(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]); + } + return aggregatorOuterIdentityIndexers[resultPositionInSignature]; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Entry aggregateEntry : mainAggregates.entrySet()) { + collector.add(packResult(aggregateEntry.getKey(), aggregateEntry.getValue())); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + // use all zero timestamps because this node cannot be used in recursive groups anyway + for (final Entry aggregateEntry : mainAggregates.entrySet()) { + collector.put(packResult(aggregateEntry.getKey(), aggregateEntry.getValue()), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + + protected Tuple packResult(Tuple signature, Object result) { + return Tuples.staticArityLeftInheritanceTupleOf(signature, result); + } + + /** + * @since 2.4 + */ + protected void aggregateUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change) { + Collection currentGroup = projection.get(signature); + // these will be null if group is empty + Object oldAggregate = mainAggregates.get(signature); + Object safeOldAggregate = oldAggregate == null ? aggregateGroup(signature, null) : oldAggregate; + boolean empty = currentGroup == null || currentGroup.isEmpty(); + Object newAggregate = empty ? null : aggregateGroupAfterUpdate(signature, currentGroup, safeOldAggregate/* + * non-null + */, + direction, updateElement, change); + if (!empty) + mainAggregates.put(signature, newAggregate); + else + mainAggregates.remove(signature); + Tuple oldTuple = packResult(signature, safeOldAggregate); + Tuple newTuple = packResult(signature, newAggregate == null ? aggregateGroup(signature, null) : newAggregate); + if (oldAggregate != null) + propagateUpdate(Direction.DELETE, oldTuple, Timestamp.ZERO); // direct outputs lack non-empty groups + if (newAggregate != null) + propagateUpdate(Direction.INSERT, newTuple, Timestamp.ZERO); // direct outputs lack non-empty groups + if (aggregatorOuterIndexer != null) + aggregatorOuterIndexer.propagate(signature, oldTuple, newTuple); + if (aggregatorOuterIdentityIndexers != null) + for (AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers) + if (aggregatorOuterIdentityIndexer != null) + aggregatorOuterIdentityIndexer.propagate(signature, oldTuple, newTuple); + } + + private Object getAggregate(Tuple signature) { + Object aggregate = mainAggregates.get(signature); + return aggregate == null ? aggregateGroup(signature, null) : aggregate; + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateToIndexerParent() && projection != null) + projection.acceptPropagatedTraceInfo(traceInfo); + } + + /** + * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original + * signature. + * + * @author Gabor Bergmann + */ + class AggregatorOuterIndexer extends StandardIndexer { + + public AggregatorOuterIndexer() { + super(me.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1)); + this.parent = me; + } + + @Override + public Collection get(Tuple signature) { + return Collections.singleton(packResult(signature, getAggregate(signature))); + } + + public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) { + propagate(Direction.INSERT, newTuple, signature, false, Timestamp.ZERO); + propagate(Direction.DELETE, oldTuple, signature, false, Timestamp.ZERO); + } + + @Override + public Node getActiveNode() { + return projection.getActiveNode(); + } + + } + + /** + * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for + * this index is the original signature of the projection index, with the suspected result inserted at position + * resultPositionInSignature. + * + * @author Gabor Bergmann + */ + + class AggregatorOuterIdentityIndexer extends StandardIndexer { + int resultPositionInSignature; + TupleMask pruneResult; + TupleMask reorderMask; + + public AggregatorOuterIdentityIndexer(int resultPositionInSignature) { + super(me.reteContainer, TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1)); + this.parent = me; + // this.localAggregates = new HashMap(); + this.resultPositionInSignature = resultPositionInSignature; + this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1); + if (resultPositionInSignature == sourceWidth) + this.reorderMask = null; + else + this.reorderMask = mask; + } + + @Override + public Collection get(Tuple signatureWithResult) { + Tuple prunedSignature = pruneResult.transform(signatureWithResult); + Object result = getAggregate(prunedSignature); + if (signatureWithResult.get(resultPositionInSignature).equals(result)) + return Collections.singleton(signatureWithResult); + else + return null; + } + + public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) { + propagate(Direction.INSERT, reorder(newTuple), signature, true, Timestamp.ZERO); + propagate(Direction.DELETE, reorder(oldTuple), signature, true, Timestamp.ZERO); + } + + private Tuple reorder(Tuple signatureWithResult) { + Tuple transformed; + if (reorderMask == null) + transformed = signatureWithResult; + else + transformed = reorderMask.transform(signatureWithResult); + return transformed; + } + + @Override + public Node getActiveNode() { + return projection.getActiveNode(); + } + } + +} 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..a9863400 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IDeltaBag; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * Faithful column aggregator with parallel aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + * + */ +public class FaithfulParallelTimelyColumnAggregatorNode extends + FaithfulTimelyColumnAggregatorNode, FoldingState> + implements ResumableNode { + + public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth)); + } + + @Override + protected Map> doFoldingStep(final Tuple group, final FoldingState state, + final Timestamp timestamp) { + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + if (state.delta.isEmpty()) { + gcAggregates(aggregate, group, timestamp); + return Collections.emptyMap(); + } else { + final Map> diffMap = CollectionsFactory.createMap(); + final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp); + + final AggregateResult currentOldResult = operator.getAggregate(aggregate.accumulator); + + for (final Entry entry : state.delta.entriesWithMultiplicities()) { + final boolean isInsertion = entry.getValue() > 0; + final Domain aggregand = entry.getKey(); + for (int i = 0; i < Math.abs(entry.getValue()); i++) { + aggregate.accumulator = operator.update(aggregate.accumulator, aggregand, isInsertion); + } + } + + final AggregateResult currentNewResult = operator.getAggregate(aggregate.accumulator); + + if (!Objects.equals(currentOldResult, currentNewResult)) { + // current old result disappears here + appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap); + } + + // current new result appears here + appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap); + } + } + + gcAggregates(aggregate, group, timestamp); + updateTimeline(group, diffMap); + + // prepare folding state for next timestamp + if (nextTimestamp != null) { + final FoldingState newState = new FoldingState<>(); + newState.delta = state.delta; + addFoldingState(group, newState, nextTimestamp); + } + + return diffMap; + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + final FoldingState state = new FoldingState<>(); + if (isInsertion) { + aggregate.aggregands.addOne(aggregand); + state.delta.addOne(aggregand); + } else { + aggregate.aggregands.removeOne(aggregand); + state.delta.removeOne(aggregand); + } + + addFoldingState(group, state, timestamp); + } + + /** + * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty. + */ + @Override + protected void gcAggregates(final CumulativeAggregate aggregate, final Tuple group, + final Timestamp timestamp) { + if (aggregate.aggregands.isEmpty()) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + groupAggregates.remove(timestamp); + if (groupAggregates.isEmpty()) { + this.aggregates.remove(group); + } + } + } + + /** + * On-demand initializes and returns the aggregate for the given group and timestamp. + */ + @Override + protected CumulativeAggregate getAggregate(final Tuple group, final Timestamp timestamp) { + final TreeMap> groupAggregates = this.aggregates + .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap()); + return groupAggregates.computeIfAbsent(timestamp, k -> { + final CumulativeAggregate aggregate = new CumulativeAggregate<>(); + final Entry> lowerEntry = groupAggregates + .lowerEntry(timestamp); + if (lowerEntry == null) { + aggregate.accumulator = operator.createNeutral(); + } else { + aggregate.accumulator = operator.clone(lowerEntry.getValue().accumulator); + } + return aggregate; + }); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final TreeMap> groupAggregates = this.aggregates.get(group); + if (groupAggregates != null) { + final Entry> lastEntry = groupAggregates.lastEntry(); + return operator.getAggregate(lastEntry.getValue().accumulator); + } else { + return NEUTRAL; + } + } + + protected static class CumulativeAggregate { + protected Accumulator accumulator; + protected IDeltaBag aggregands; + + protected CumulativeAggregate() { + this.aggregands = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "accumulator=" + accumulator + " aggregands=" + aggregands; + } + } + + protected static class FoldingState implements MergeableFoldingState> { + protected IDeltaBag delta; + + protected FoldingState() { + this.delta = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "delta=" + delta; + } + + /** + * The returned result will never be null, even if the resulting delta set is empty. + */ + @Override + public FoldingState merge(final FoldingState that) { + Preconditions.checkArgument(that != null); + // 'this' was the previously registered folding state + // 'that' is the new folding state being pushed upwards + final FoldingState result = new FoldingState<>(); + this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + return result; + } + + } + +} \ No newline at end of file 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..666b2051 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java @@ -0,0 +1,280 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IDeltaBag; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * Faithful column aggregator with sequential aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + * + */ +public class FaithfulSequentialTimelyColumnAggregatorNode extends + FaithfulTimelyColumnAggregatorNode, FoldingState> + implements ResumableNode { + + protected boolean isRecursiveAggregation; + + public FaithfulSequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.isRecursiveAggregation = false; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.isRecursiveAggregation = this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + } + + @Override + protected Map> doFoldingStep(final Tuple group, + final FoldingState state, final Timestamp timestamp) { + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + if (state.delta.isEmpty() && Objects.equals(state.oldResult, state.newResult)) { + gcAggregates(aggregate, group, timestamp); + return Collections.emptyMap(); + } else { + final Map> diffMap = CollectionsFactory.createMap(); + final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp); + + final AggregateResult previousOldResult = state.oldResult; + final AggregateResult previousNewResult = state.newResult; + + final AggregateResult currentOldResult = previousOldResult == null + ? operator.getAggregate(aggregate.positive) + : operator.combine(previousOldResult, aggregate.positive); + + for (final Entry entry : state.delta.entriesWithMultiplicities()) { + final boolean isInsertion = entry.getValue() > 0; + final Domain aggregand = entry.getKey(); + if (isInsertion) { + for (int i = 0; i < entry.getValue(); i++) { + if (isRecursiveAggregation) { + final boolean contains = aggregate.negative.containsNonZero(aggregand); + if (contains) { + aggregate.negative.addOne(aggregand); + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, true); + } + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, true); + } + } + } else { + for (int i = 0; i < -entry.getValue(); i++) { + if (isRecursiveAggregation) { + final boolean contains = operator.contains(aggregand, aggregate.positive); + if (contains) { + aggregate.positive = operator.update(aggregate.positive, aggregand, false); + } else { + aggregate.negative.removeOne(aggregand); + } + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, false); + } + } + } + } + + final AggregateResult currentNewResult = previousNewResult == null + ? operator.getAggregate(aggregate.positive) + : operator.combine(previousNewResult, aggregate.positive); + + aggregate.cachedResult = currentNewResult; + + final boolean sameResult = Objects.equals(currentOldResult, currentNewResult); + if (!sameResult) { + // current old result disappears here + appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap); + } + + // current new result appears here + appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap); + } + } + + gcAggregates(aggregate, group, timestamp); + updateTimeline(group, diffMap); + + // prepare folding state for next timestamp + if (nextTimestamp != null && !sameResult) { + final FoldingState newState = new FoldingState<>(); + // DO NOT push forward the delta in the folding state!!! that one only affects the input timestamp + newState.oldResult = currentOldResult; + newState.newResult = currentNewResult; + addFoldingState(group, newState, nextTimestamp); + } + + return diffMap; + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + final FoldingState state = new FoldingState(); + if (isInsertion) { + state.delta.addOne(aggregand); + } else { + state.delta.removeOne(aggregand); + } + state.oldResult = previousResult; + state.newResult = previousResult; + + // it is acceptable if both oldResult and newResult are null at this point + // in that case we did not have a previous entry at a lower timestamp + + addFoldingState(group, state, timestamp); + } + + protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) { + final TreeMap> entryMap = this.aggregates + .get(group); + if (entryMap == null) { + return null; + } else { + CumulativeAggregate aggregate = null; + if (lower) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry != null) { + aggregate = lowerEntry.getValue(); + } + } else { + aggregate = entryMap.get(timestamp); + } + if (aggregate == null) { + return null; + } else { + return aggregate.cachedResult; + } + } + } + + @Override + protected void gcAggregates(final CumulativeAggregate aggregate, + final Tuple group, final Timestamp timestamp) { + if (operator.isNeutral(aggregate.positive) && aggregate.negative.isEmpty()) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + groupAggregates.remove(timestamp); + if (groupAggregates.isEmpty()) { + this.aggregates.remove(group); + } + } + } + + @Override + protected CumulativeAggregate getAggregate(final Tuple group, + final Timestamp timestamp) { + final TreeMap> groupAggregates = this.aggregates + .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap()); + return groupAggregates.computeIfAbsent(timestamp, k -> { + final CumulativeAggregate aggregate = new CumulativeAggregate<>(); + aggregate.positive = operator.createNeutral(); + return aggregate; + }); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + if (groupAggregates != null) { + final Entry> lastEntry = groupAggregates + .lastEntry(); + return lastEntry.getValue().cachedResult; + } else { + return NEUTRAL; + } + } + + protected static class CumulativeAggregate { + protected Accumulator positive; + protected IDeltaBag negative; + protected AggregateResult cachedResult; + + protected CumulativeAggregate() { + this.negative = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "positive=" + positive + " negative=" + negative + " cachedResult=" + cachedResult; + } + } + + protected static class FoldingState + implements MergeableFoldingState> { + protected IDeltaBag delta; + protected AggregateResult oldResult; + protected AggregateResult newResult; + + protected FoldingState() { + this.delta = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "delta=" + delta + " oldResult=" + oldResult + " newResult=" + newResult; + } + + /** + * The returned result will never be null, even if the resulting delta set is empty. + */ + @Override + public FoldingState merge(final FoldingState that) { + Preconditions.checkArgument(that != null); + // 'this' was the previously registered folding state + // 'that' is the new folding state being pushed upwards + final FoldingState result = new FoldingState(); + this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + result.oldResult = this.oldResult; + result.newResult = that.newResult; + return result; + } + + } + +} \ No newline at end of file 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * Faithful timely implementation of the column aggregator node. Complete timelines (series of appearance & + * disappearance) are maintained for tuples.
+ *
+ * Subclasses are responsible for implementing the aggregator architecture, and they must use the CumulativeAggregate + * type parameter for that.
+ *
+ * This node supports recursive aggregation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public abstract class FaithfulTimelyColumnAggregatorNode> + extends AbstractColumnAggregatorNode implements ResumableNode { + + protected final Map> aggregates; + protected final Map>> timelines; + protected final TreeMap> foldingState; + protected CommunicationGroup communicationGroup; + + public FaithfulTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.aggregates = CollectionsFactory.createMap(); + this.timelines = CollectionsFactory.createMap(); + this.foldingState = CollectionsFactory.createTreeMap(); + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + @Override + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void clear() { + this.mailbox.clear(); + this.aggregates.clear(); + this.timelines.clear(); + this.children.clear(); + this.childMailboxes.clear(); + this.foldingState.clear(); + } + + /** + * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the + * two states will be merged together. + * + * + */ + protected void addFoldingState(final Tuple group, final FoldingState state, final Timestamp timestamp) { + // assert !state.delta.isEmpty(); + final Map tupleMap = this.foldingState.computeIfAbsent(timestamp, + k -> CollectionsFactory.createMap()); + tupleMap.compute(group, (k, v) -> { + return v == null ? state : v.merge(state); + }); + } + + @Override + public Timestamp getResumableTimestamp() { + if (this.foldingState.isEmpty()) { + return null; + } else { + return this.foldingState.firstKey(); + } + } + + @Override + public void resumeAt(final Timestamp timestamp) { + Timestamp current = this.getResumableTimestamp(); + if (current == null) { + throw new IllegalStateException("There is nothing to fold!"); + } else if (current.compareTo(timestamp) != 0) { + throw new IllegalStateException("Expected to continue folding at " + timestamp + "!"); + } + + final Map tupleMap = this.foldingState.remove(timestamp); + for (final Entry groupEntry : tupleMap.entrySet()) { + final Tuple group = groupEntry.getKey(); + final FoldingState value = groupEntry.getValue(); + final Map> diffMap = doFoldingStep(group, value, timestamp); + for (final Entry> resultEntry : diffMap.entrySet()) { + for (final Signed signed : resultEntry.getValue()) { + propagate(signed.getDirection(), group, resultEntry.getKey(), signed.getPayload()); + } + } + } + + final Timestamp nextTimestamp = this.getResumableTimestamp(); + if (Objects.equals(timestamp, nextTimestamp)) { + throw new IllegalStateException( + "Folding at " + timestamp + " produced more folding work at the same timestamp!"); + } else if (nextTimestamp != null) { + this.communicationGroup.notifyHasMessage(this.mailbox, nextTimestamp); + } + } + + protected abstract Map> doFoldingStep(final Tuple group, final FoldingState state, + final Timestamp timestamp); + + /** + * Updates and garbage collects the timeline of the given tuple based on the given diffs. + */ + protected void updateTimeline(final Tuple group, final Map> diffs) { + if (!diffs.isEmpty()) { + this.timelines.compute(group, (k, resultTimelines) -> { + if (resultTimelines == null) { + resultTimelines = CollectionsFactory.createMap(); + } + for (final Entry> entry : diffs.entrySet()) { + final AggregateResult result = entry.getKey(); + resultTimelines.compute(result, (k2, oldResultTimeline) -> { + final Diff currentResultDiffs = entry.getValue(); + if (oldResultTimeline == null) { + oldResultTimeline = getInitialTimeline(result); + } + final Timeline timeline = oldResultTimeline.mergeAdditive(currentResultDiffs); + if (timeline.isEmpty()) { + return null; + } else { + return timeline; + } + }); + } + if (resultTimelines.isEmpty()) { + return null; + } else { + return resultTimelines; + } + }); + } + } + + /** + * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty. + */ + protected abstract void gcAggregates(final CumulativeAggregate aggregate, final Tuple group, + final Timestamp timestamp); + + /** + * On-demand initializes and returns the aggregate for the given group and timestamp. + */ + protected abstract CumulativeAggregate getAggregate(final Tuple group, final Timestamp timestamp); + + protected static final Timeline NEUTRAL_INITIAL_TIMELINE = Timestamp.INSERT_AT_ZERO_TIMELINE; + protected static final Timeline NON_NEUTRAL_INITIAL_TIMELINE = Timelines.createEmpty(); + + protected Timeline getInitialTimeline(final AggregateResult result) { + if (NEUTRAL == result) { + return NEUTRAL_INITIAL_TIMELINE; + } else { + return NON_NEUTRAL_INITIAL_TIMELINE; + } + } + + protected static void appendDiff(final AggregateResult result, final Signed diff, + final Map> diffs) { + if (result != null) { + diffs.compute(result, (k, timeLineDiff) -> { + if (timeLineDiff == null) { + timeLineDiff = new Diff<>(); + } + timeLineDiff.add(diff); + return timeLineDiff; + }); + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + return tupleFromAggregateResult(group, getAggregateResult(group)); + } + + @Override + public Map> getAggregateResultTimeline(final Tuple group) { + final Map> resultTimelines = this.timelines.get(group); + if (resultTimelines == null) { + if (NEUTRAL == null) { + return Collections.emptyMap(); + } else { + return Collections.singletonMap(NEUTRAL, NEUTRAL_INITIAL_TIMELINE); + } + } else { + return resultTimelines; + } + } + + @Override + public Map> getAggregateTupleTimeline(final Tuple group) { + final Map> resultTimelines = getAggregateResultTimeline(group); + return new GroupedMap>(group, resultTimelines, this.runtimeContext); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return communicationGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.communicationGroup = currentGroup; + } + + protected interface MergeableFoldingState { + + public abstract T merge(final T that); + + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Map.Entry; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * First-only column aggregator with parallel aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class FirstOnlyParallelTimelyColumnAggregatorNode + extends FirstOnlyTimelyColumnAggregatorNode { + + public FirstOnlyParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + /** + * Accumulator gets modified at the input timestamp and at all higher timestamps. Folding cannot be interrupted if + * the new aggregate result is the same as the old at an intermediate timestamp because aggregands need to be copied + * over to all accumulators at the higher timestamps. + */ + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + + Accumulator oldAccumulator = getAccumulator(group, timestamp); + AggregateResult oldResult = operator.getAggregate(oldAccumulator); + + Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + AggregateResult newResult = operator.getAggregate(newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, timestamp); + + propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult); + + AggregateResult previousOldResult = oldResult; + AggregateResult previousNewResult = newResult; + final TreeMap> groupEntries = this.memory + .get(group); + + Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp); + + while (currentTimestamp != null) { + final CumulativeAggregate groupEntry = groupEntries.get(currentTimestamp); + oldResult = groupEntry.result; + oldAccumulator = groupEntry.accumulator; + newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + newResult = operator.getAggregate(newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, currentTimestamp); + + propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, oldResult, newResult); + + previousOldResult = oldResult; + previousNewResult = newResult; + currentTimestamp = groupEntries.higherKey(currentTimestamp); + } + } + + @Override + protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return operator.createNeutral(); + } else { + final CumulativeAggregate entry = entryMap.get(timestamp); + if (entry == null) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry == null) { + return operator.createNeutral(); + } else { + return operator.clone(lowerEntry.getValue().accumulator); + } + } else { + return entry.accumulator; + } + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * First-only column aggregator with sequential aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class FirstOnlySequentialTimelyColumnAggregatorNode + extends FirstOnlyTimelyColumnAggregatorNode { + + public FirstOnlySequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + /** + * Accumulator gets modified only at the timestamp where the update happened. During the folding, accumulators are + * never changed at higher timestamps. Aggregate results at higher timestamps may change due to the change at the + * input timestamp. Uniqueness enforcement may require from aggregate results to jump up/down on demand during the + * folding. + */ + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + + final Accumulator oldAccumulator = getAccumulator(group, timestamp); + final AggregateResult oldResult = previousResult == null ? operator.getAggregate(oldAccumulator) + : operator.combine(previousResult, oldAccumulator); + + final Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + final AggregateResult newResult = previousResult == null ? operator.getAggregate(newAccumulator) + : operator.combine(previousResult, newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, timestamp); + + propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult); + + // fold up the state towards higher timestamps + if (!Objects.equals(oldResult, newResult)) { + AggregateResult previousOldResult = oldResult; + AggregateResult previousNewResult = newResult; + AggregateResult currentOldResult = null; + AggregateResult currentNewResult = null; + final TreeMap> groupEntries = this.memory + .get(group); + + Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp); + + while (currentTimestamp != null) { + // they cannot be the same, otherwise we would not even be here + assert !Objects.equals(previousOldResult, previousNewResult); + + final Accumulator accumulator = getAccumulator(group, currentTimestamp); + currentOldResult = groupEntries.get(currentTimestamp).result; + currentNewResult = operator.combine(previousNewResult, accumulator); + + // otherwise we would not be iterating over this timestamp + assert !operator.isNeutral(accumulator); + + propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, currentOldResult, + currentNewResult); + + if (!Objects.equals(currentOldResult, currentNewResult)) { + storeIfNotNeutral(group, accumulator, currentNewResult, currentTimestamp); + previousOldResult = currentOldResult; + previousNewResult = currentNewResult; + currentTimestamp = groupEntries.higherKey(currentTimestamp); + } else { + // we can stop the folding from here + break; + } + } + } + } + + @Override + protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return operator.createNeutral(); + } else { + final CumulativeAggregate entry = entryMap.get(timestamp); + if (entry == null) { + return operator.createNeutral(); + } else { + return entry.accumulator; + } + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * First-only timely implementation of the column aggregator node. Only timestamps of appearance are maintained for + * tuples instead of complete timelines. + *

+ * Subclasses are responsible for implementing the aggregator architecture, and they must make use of the inner class {@link CumulativeAggregate}. + *

+ * This node supports recursive aggregation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public abstract class FirstOnlyTimelyColumnAggregatorNode + extends AbstractColumnAggregatorNode { + + protected final Map>> memory; + + public FirstOnlyTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.memory = CollectionsFactory.createMap(); + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + protected static class CumulativeAggregate { + // the accumulator storing the aggregands + protected Accumulator accumulator; + // the aggregate result at the timestamp where this cumulative aggregate is stored + protected AggregateResult result; + + private CumulativeAggregate(final Accumulator accumulator, final AggregateResult result) { + this.accumulator = accumulator; + this.result = result; + } + + } + + public Collection getGroups() { + return this.memory.keySet(); + } + + public AggregateResult getLastResult(final Tuple group) { + final TreeMap> groupMap = this.memory.get(group); + if (groupMap == null) { + return null; + } else { + return groupMap.lastEntry().getValue().result; + } + } + + public Timestamp getLastTimestamp(final Tuple group) { + final TreeMap> groupMap = this.memory.get(group); + if (groupMap == null) { + return null; + } else { + return groupMap.lastEntry().getKey(); + } + } + + @Override + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void clear() { + this.mailbox.clear(); + this.memory.clear(); + this.children.clear(); + this.childMailboxes.clear(); + } + + protected void propagateWithChecks(final Tuple group, final Timestamp timestamp, + final AggregateResult previousOldResult, final AggregateResult previousNewResult, + final AggregateResult currentOldResult, final AggregateResult currentNewResult) { + final boolean jumpDown = Objects.equals(previousNewResult, currentOldResult); + final boolean jumpUp = Objects.equals(previousOldResult, currentNewResult); + final boolean resultsDiffer = !Objects.equals(currentOldResult, currentNewResult); + + // uniqueness enforcement is happening here + if ((resultsDiffer || jumpDown) && !Objects.equals(previousOldResult, currentOldResult)) { + propagate(Direction.DELETE, group, currentOldResult, timestamp); + } + if ((resultsDiffer || jumpUp) && !Objects.equals(previousNewResult, currentNewResult)) { + propagate(Direction.INSERT, group, currentNewResult, timestamp); + } + } + + /** + * Returns the aggregation architecture-specific accumulator at the specified timestamp for the given group. + */ + protected abstract Accumulator getAccumulator(final Tuple group, final Timestamp timestamp); + + protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return null; + } else { + CumulativeAggregate entry = null; + if (lower) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry != null) { + entry = lowerEntry.getValue(); + } + } else { + entry = entryMap.get(timestamp); + } + if (entry == null) { + return null; + } else { + return entry.result; + } + } + } + + protected AggregateResult getResult(final Tuple group, final Timestamp timestamp, final boolean lower) { + final AggregateResult result = getResultRaw(group, timestamp, lower); + if (result == null) { + return NEUTRAL; + } else { + return result; + } + } + + protected AggregateResult getResult(final Tuple group, final Timestamp timestamp) { + return getResult(group, timestamp, false); + } + + protected void storeIfNotNeutral(final Tuple group, final Accumulator accumulator, final AggregateResult value, + final Timestamp timestamp) { + TreeMap> entryMap = this.memory.get(group); + if (operator.isNeutral(accumulator)) { + if (entryMap != null) { + entryMap.remove(timestamp); + if (entryMap.isEmpty()) { + this.memory.remove(group); + } + } + } else { + if (entryMap == null) { + entryMap = CollectionsFactory.createTreeMap(); + this.memory.put(group, entryMap); + } + entryMap.put(timestamp, new CumulativeAggregate<>(accumulator, value)); + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + return tupleFromAggregateResult(group, getResult(group, Timestamp.ZERO)); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + return getResult(group, Timestamp.ZERO); + } + + @Override + public Map> getAggregateResultTimeline(final Tuple group) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return Collections.emptyMap(); + } else { + final Map> result = CollectionsFactory.createMap(); + for (final Entry> entry : entryMap + .descendingMap().entrySet()) { + result.put(entry.getValue().result, Timelines.createFrom(entry.getKey())); + } + return result; + } + } + + @Override + public Map> getAggregateTupleTimeline(final Tuple group) { + final Map> resultTimelines = getAggregateResultTimeline(group); + return new GroupedMap>(group, resultTimelines, this.runtimeContext); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.boundary; + +/** + * For objects that connect a RETE implementation to the underlying model. + * + * @author Gabor Bergmann + * + */ +public interface Disconnectable { + + /** + * Disconnects this rete engine component from the underlying model. Disconnecting enables the garbage collection + * mechanisms to dispose of the rete network. + */ + void disconnect(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.remote.Address; + +/** + * An input node representing an enumerable extensional input relation and receiving external updates. + * + *

+ * Contains those tuples that are in the extensional relation identified by the input key, and also conform to the + * global seed (if any). + * + * @author Bergmann Gabor + * + */ +public class ExternalInputEnumeratorNode extends StandardNode + implements Disconnectable, Receiver, IQueryRuntimeContextListener { + + private IQueryRuntimeContext context = null; + private IInputKey inputKey; + private Tuple globalSeed; + private InputConnector inputConnector; + private Network network; + private Address myAddress; + private boolean parallelExecutionEnabled; + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + private final IQueryBackendContext qBackendContext; + + public ExternalInputEnumeratorNode(ReteContainer reteContainer) { + super(reteContainer); + myAddress = Address.of(this); + network = reteContainer.getNetwork(); + inputConnector = network.getInputConnector(); + qBackendContext = network.getEngine().getBackendContext(); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own + * mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + public void connectThroughContext(ReteEngine engine, IInputKey inputKey, Tuple globalSeed) { + this.inputKey = inputKey; + this.globalSeed = globalSeed; + setTag(inputKey); + + final IQueryRuntimeContext context = engine.getRuntimeContext(); + if (!context.getMetaContext().isEnumerable(inputKey)) + throw new IllegalArgumentException(this.getClass().getSimpleName() + + " only applicable for enumerable input keys; received instead " + inputKey); + + this.context = context; + this.parallelExecutionEnabled = engine.isParallelExecutionEnabled(); + + engine.addDisconnectable(this); + context.addUpdateListener(inputKey, globalSeed, this); + } + + @Override + public void disconnect() { + if (context != null) { // if connected + context.removeUpdateListener(inputKey, globalSeed, this); + context = null; + } + } + + /** + * @since 2.2 + */ + protected Iterable getTuplesInternal() { + Iterable tuples = null; + + if (context != null) { // if connected + if (globalSeed == null) { + tuples = context.enumerateTuples(inputKey, TupleMask.empty(inputKey.getArity()), + Tuples.staticArityFlatTupleOf()); + } else { + final TupleMask mask = TupleMask.fromNonNullIndices(globalSeed); + tuples = context.enumerateTuples(inputKey, mask, mask.transform(globalSeed)); + } + } + + return tuples; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + final Iterable tuples = getTuplesInternal(); + if (tuples != null) { + for (final Tuple tuple : tuples) { + collector.add(tuple); + } + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Iterable tuples = getTuplesInternal(); + if (tuples != null) { + for (final Tuple tuple : tuples) { + collector.put(tuple, Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + } + + /* Update from runtime context */ + @Override + public void update(IInputKey key, Tuple update, boolean isInsertion) { + if (parallelExecutionEnabled) { + // send back to myself as an official external update, and then propagate it transparently + network.sendExternalUpdate(myAddress, direction(isInsertion), update); + } else { + if (qBackendContext.areUpdatesDelayed()) { + // post the update into the mailbox of the node + mailbox.postMessage(direction(isInsertion), update, Timestamp.ZERO); + } else { + // just propagate the input + update(direction(isInsertion), update, Timestamp.ZERO); + } + // if the the update method is called from within a delayed execution, + // the following invocation will be a no-op + network.waitForReteTermination(); + } + } + + private static Direction direction(boolean isInsertion) { + return isInsertion ? Direction.INSERT : Direction.DELETE; + } + + /* Self-addressed from network */ + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + + @Override + public void appendParent(Supplier supplier) { + throw new UnsupportedOperationException("Input nodes can't have parents"); + } + + @Override + public void removeParent(Supplier supplier) { + throw new UnsupportedOperationException("Input nodes can't have parents"); + } + + @Override + public Collection getParents() { + return Collections.emptySet(); + } + + public IInputKey getInputKey() { + return inputKey; + } + + public Tuple getGlobalSeed() { + return globalSeed; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.single.FilterNode; + +/** + * A filter node representing a (stateless, typically non-enumerable) extensional input relation. + * + *

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. + * + * @author Bergmann Gabor + * + */ +public class ExternalInputStatelessFilterNode extends FilterNode implements Disconnectable { + + IQueryRuntimeContext context = null; + IInputKey inputKey; + private InputConnector inputConnector; + private TupleMask mask; + + public ExternalInputStatelessFilterNode(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.mask = mask; + this.inputConnector = reteContainer.getNetwork().getInputConnector(); + } + + @Override + public boolean check(Tuple ps) { + if (mask != null) + ps = mask.transform(ps); + return context.containsTuple(inputKey, ps); + } + + + public void connectThroughContext(ReteEngine engine, IInputKey inputKey) { + this.inputKey = inputKey; + setTag(inputKey); + + final IQueryRuntimeContext context = engine.getRuntimeContext(); + if (!context.getMetaContext().isStateless(inputKey)) + throw new IllegalArgumentException( + this.getClass().getSimpleName() + + " only applicable for stateless input keys; received instead " + + inputKey); + + this.context = context; + + engine.addDisconnectable(this); + } + + @Override + public void disconnect() { + this.context = null; + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.recipes.InputFilterRecipe; +import tools.refinery.viatra.runtime.rete.recipes.InputRecipe; +import tools.refinery.viatra.runtime.rete.remote.Address; + +/** + * A class responsible for connecting input nodes to the runtime context. + * + * @author Bergmann Gabor + * + */ +public final class InputConnector { + Network network; + + private Map>> externalInputRoots = CollectionsFactory.createMap(); + +// /* +// * arity:1 used as simple entity constraints label is the object representing the type null label means all entities +// * regardless of type (global supertype), if allowed +// */ +// protected Map> unaryRoots = CollectionsFactory.getMap(); +// /* +// * arity:3 (rel, from, to) used as VPM relation constraints null label means all relations regardless of type +// * (global supertype) +// */ +// protected Map> ternaryEdgeRoots = CollectionsFactory.getMap(); +// /* +// * arity:2 (from, to) not used over VPM; can be used as EMF references for instance label is the object representing +// * the type null label means all entities regardless of type if allowed (global supertype), if allowed +// */ +// protected Map> binaryEdgeRoots = CollectionsFactory.getMap(); +// +// protected Address containmentRoot = null; +// protected Address containmentTransitiveRoot = null; +// protected Address instantiationRoot = null; +// protected Address instantiationTransitiveRoot = null; +// protected Address generalizationRoot = null; +// protected Address generalizationTransitiveRoot = null; + + + public InputConnector(Network network) { + super(); + this.network = network; + } + + + public Network getNetwork() { + return network; + } + + + /** + * Connects a given input filter node to the external input source. + */ + public void connectInputFilter(InputFilterRecipe recipe, Node freshNode) { + final ExternalInputStatelessFilterNode inputNode = (ExternalInputStatelessFilterNode)freshNode; + + IInputKey inputKey = (IInputKey) recipe.getInputKey(); + inputNode.connectThroughContext(network.getEngine(), inputKey); + } + + + /** + * Connects a given input enumerator node to the external input source. + */ + public void connectInput(InputRecipe recipe, Node freshNode) { + final ExternalInputEnumeratorNode inputNode = (ExternalInputEnumeratorNode)freshNode; + + IInputKey inputKey = (IInputKey) recipe.getInputKey(); + Tuple seed = nopSeed(inputKey); // no preseeding as of now + final Address freshAddress = Address.of(inputNode); + externalInputRoots.computeIfAbsent(inputKey, k -> CollectionsFactory.createMap()).put(seed, freshAddress); + inputNode.connectThroughContext(network.getEngine(), inputKey, seed); + +// final Address freshAddress = Address.of((Tunnel)freshNode); +// if (recipe instanceof TypeInputRecipe) { +// final Object typeKey = ((TypeInputRecipe) recipe).getTypeKey(); +// +// if (recipe instanceof UnaryInputRecipe) { +// unaryRoots.put(typeKey, freshAddress); +// new EntityFeeder(freshAddress, this, typeKey).feed(); +//// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +//// Collection subTypes = context.enumerateDirectUnarySubtypes(typeObject); +//// +//// for (Object subType : subTypes) { +//// Address subRoot = accessUnaryRoot(subType); +//// network.connectRemoteNodes(subRoot, tn, true); +//// } +//// } +// } else if (recipe instanceof BinaryInputRecipe) { +// binaryEdgeRoots.put(typeKey, freshAddress); +// externalInputRoots.put(rowKey, columnKey, freshAddress); +// new ReferenceFeeder(freshAddress, this, typeKey).feed(); +// // if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// // Collection subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject); +// // +// // for (Object subType : subTypes) { +// // Address subRoot = accessTernaryEdgeRoot(subType); +// // network.connectRemoteNodes(subRoot, tn, true); +// // } +// // } +// } +// +// +// } + + } + +// /** +// * fetches the entity Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getUnaryRoot(Object label) { +// return unaryRoots.get(label); +// } +// +// public Collection> getAllUnaryRoots() { +// return unaryRoots.values(); +// } +// +// /** +// * fetches the relation Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getTernaryEdgeRoot(Object label) { +// return ternaryEdgeRoots.get(label); +// } +// +// public Collection> getAllTernaryEdgeRoots() { +// return ternaryEdgeRoots.values(); +// } +// +// /** +// * fetches the reference Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getBinaryEdgeRoot(Object label) { +// return binaryEdgeRoots.get(label); +// } +// +// public Collection> getAllBinaryEdgeRoots() { +// return binaryEdgeRoots.values(); +// } +// +// +// public Address getContainmentRoot() { +// return containmentRoot; +// } +// +// +// public Address getContainmentTransitiveRoot() { +// return containmentTransitiveRoot; +// } +// +// +// public Address getInstantiationRoot() { +// return instantiationRoot; +// } +// +// +// public Address getInstantiationTransitiveRoot() { +// return instantiationTransitiveRoot; +// } +// +// +// public Address getGeneralizationRoot() { +// return generalizationRoot; +// } + + + public Stream> getAllExternalInputNodes() { + return externalInputRoots.values().stream().flatMap(map -> map.values().stream()); + } + public Collection> getAllExternalInputNodesForKey(IInputKey inputKey) { + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).values(); + } + public Address getExternalInputNodeForKeyUnseeded(IInputKey inputKey) { + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(nopSeed(inputKey)); + } + public Address getExternalInputNode(IInputKey inputKey, Tuple seed) { + if (seed == null) seed = nopSeed(inputKey); + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(seed); + } + + + Tuple nopSeed(IInputKey inputKey) { + return Tuples.flatTupleOf(new Object[inputKey.getArity()]); + } + + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * Responsible for the storage, maintenance and communication of the nodes of the network that are accessible form the + * outside for various reasons. + * + * @author Gabor Bergmann + * + *

TODO: should eventually be merged into {@link InputConnector} and deleted + * + */ +public class ReteBoundary /*implements IPatternMatcherRuntimeContextListener*/ { + + protected ReteEngine engine; + protected Network network; + protected ReteContainer headContainer; + + public ReteContainer getHeadContainer() { + return headContainer; + } + + protected final InputConnector inputConnector; + + + protected Map> subplanToAddressMapping; + + + /** + * SubPlans of parent nodes that have the key node as their child. For RETE --> SubPlan traceability, mainly at production + * nodes. + */ + protected Map, Set> parentPlansOfReceiver; + + /** + * Prerequisite: engine has its network and framework fields initialized + */ + public ReteBoundary(ReteEngine engine) { + super(); + this.engine = engine; + this.network = engine.getReteNet(); + this.headContainer = network.getHeadContainer(); + inputConnector = network.getInputConnector(); + + this.parentPlansOfReceiver = CollectionsFactory.createMap(); + + // productionsScoped = new HashMap,Address>>(); + subplanToAddressMapping = CollectionsFactory.createMap(); + + } + + public Collection getAllProductionNodes() { + return engine.getCompiler().getCachedCompiledQueries().values(); + } + +// /** +// * accesses the entity Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessUnaryRoot(Object typeObject) { +// Address tn; +// tn = unaryRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(1, typeObject); +// unaryRoots.put(typeObject, tn); +// +// new EntityFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectUnarySubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessUnaryRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// +// } +// return tn; +// } +// +// /** +// * accesses the relation Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessTernaryEdgeRoot(Object typeObject) { +// Address tn; +// tn = ternaryEdgeRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(3, typeObject); +// ternaryEdgeRoots.put(typeObject, tn); +// +// new RelationFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessTernaryEdgeRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// } +// return tn; +// } +// +// /** +// * accesses the reference Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessBinaryEdgeRoot(Object typeObject) { +// Address tn; +// tn = binaryEdgeRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(2, typeObject); +// binaryEdgeRoots.put(typeObject, tn); +// +// new ReferenceFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectBinaryEdgeSubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessBinaryEdgeRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// } +// return tn; +// } +// +// /** +// * accesses the special direct containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessContainmentRoot() { +// if (containmentRoot == null) { +// // containment: relation quasi-type +// containmentRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$containment"); +// +// new ContainmentFeeder(containmentRoot, context, network, this).feed(); +// } +// return containmentRoot; +// } +// +// /** +// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessContainmentTransitiveRoot() { +// if (containmentTransitiveRoot == null) { +// // transitive containment: derived +// Address containmentTransitiveRoot = headContainer.getProvisioner().newUniquenessEnforcerNode( +// 2, "$containmentTransitive"); +// network.connectRemoteNodes(accessContainmentRoot(), containmentTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessContainmentRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// containmentTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, containmentTransitiveRoot, true); +// +// this.containmentTransitiveRoot = containmentTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return containmentTransitiveRoot; +// } +// +// /** +// * accesses the special instantiation relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessInstantiationRoot() { +// if (instantiationRoot == null) { +// // instantiation: relation quasi-type +// instantiationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$instantiation"); +// +// new InstantiationFeeder(instantiationRoot, context, network, this).feed(); +// } +// return instantiationRoot; +// } +// +// /** +// * accesses the special transitive instantiation relation Root node; creates the node if it doesn't exist yet +// * InstantiationTransitive = Instantiation o (Generalization)^* +// */ +// public Address accessInstantiationTransitiveRoot() { +// if (instantiationTransitiveRoot == null) { +// // transitive instantiation: derived +// Address instantiationTransitiveRoot = headContainer.getProvisioner() +// .newUniquenessEnforcerNode(2, "$instantiationTransitive"); +// network.connectRemoteNodes(accessInstantiationRoot(), instantiationTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// instantiationTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, +// new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, instantiationTransitiveRoot, true); +// +// this.instantiationTransitiveRoot = instantiationTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return instantiationTransitiveRoot; +// } +// +// /** +// * accesses the special generalization relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessGeneralizationRoot() { +// if (generalizationRoot == null) { +// // generalization: relation quasi-type +// generalizationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$generalization"); +// +// new GeneralizationFeeder(generalizationRoot, context, network, this).feed(); +// } +// return generalizationRoot; +// } +// +// /** +// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessGeneralizationTransitiveRoot() { +// if (generalizationTransitiveRoot == null) { +// // transitive generalization: derived +// Address generalizationTransitiveRoot = headContainer.getProvisioner() +// .newUniquenessEnforcerNode(2, "$generalizationTransitive"); +// network.connectRemoteNodes(accessGeneralizationRoot(), generalizationTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// generalizationTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, generalizationTransitiveRoot, true); +// +// this.generalizationTransitiveRoot = generalizationTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return generalizationTransitiveRoot; +// } + + // /** + // * Registers and publishes a supplier under specified label. + // */ + // public void publishSupplier(Supplier s, Object label) + // { + // publishedSuppliers.put(label, s); + // } + // + // /** + // * fetches the production node under specified label; + // * returns null if it doesn't exist yet + // */ + // public Production getProductionNode(Object label) + // { + // return productions.get(label); + // } + // + // /** + // * fetches the published supplier under specified label; + // * returns null if it doesn't exist yet + // */ + // public Supplier getPublishedSupplier(Object label) + // { + // return publishedSuppliers.get(label); + // } + + /** + * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet + * @throws ViatraQueryRuntimeException + */ + public synchronized RecipeTraceInfo accessProductionTrace(PQuery query) + { + final CompiledQuery compiled = engine.getCompiler().getCompiledForm(query); + return compiled; +// RecipeTraceInfo pn; +// pn = queryPlans.get(query); +// if (pn == null) { +// pn = construct(query); +// TODO handle recursion by reinterpret-RecipeTrace +// queryPlans.put(query, pn); +// if (pn == null) { +// String[] args = { query.toString() }; +// throw new RetePatternBuildException("Unsuccessful planning of RETE construction recipe for query {1}", +// args, "Could not create RETE recipe plan.", query); +// } +// } +// return pn; + } + /** + * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet + * @throws ViatraQueryRuntimeException + */ + public synchronized Address accessProductionNode(PQuery query) { + final RecipeTraceInfo productionTrace = accessProductionTrace(query); + return (Address) headContainer.getProvisioner().getOrCreateNodeByRecipe(productionTrace); + } + +// /** +// * creates the production node for the specified pattern Contract: only call from the builder (through Buildable) +// * responsible for building this pattern +// * +// * @throws PatternMatcherCompileTimeException +// * if production node is already created +// */ +// public synchronized Address createProductionInternal(PQuery gtPattern) +// throws QueryPlannerException { +// if (queryPlans.containsKey(gtPattern)) { +// String[] args = { gtPattern.toString() }; +// throw new RetePatternBuildException("Multiple creation attempts of production node for {1}", args, +// "Duplicate RETE production node.", gtPattern); +// } +// +// Map posMapping = engine.getBuilder().getPosMapping(gtPattern); +// Address pn = headContainer.getProvisioner().newProductionNode(posMapping, gtPattern); +// queryPlans.put(gtPattern, pn); +// context.reportPatternDependency(gtPattern); +// +// return pn; +// } + + // /** + // * accesses the production node for specified pattern and scope map; creates the node if + // * it doesn't exist yet + // */ + // public synchronized Address accessProductionScoped( + // GTPattern gtPattern, Map additionalScopeMap) throws PatternMatcherCompileTimeException { + // if (additionalScopeMap.isEmpty()) return accessProduction(gtPattern); + // + // Address pn; + // + // Map, Address> scopes = productionsScoped.get(gtPattern); + // if (scopes == null) { + // scopes = new HashMap, Address>(); + // productionsScoped.put(gtPattern, scopes); + // } + // + // pn = scopes.get(additionalScopeMap); + // if (pn == null) { + // Address unscopedProduction = accessProduction(gtPattern); + // + // HashMap posMapping = headContainer.resolveLocal(unscopedProduction).getPosMapping(); + // pn = headContainer.getLibrary().newProductionNode(posMapping); + // scopes.put(additionalScopeMap, pn); + // + // constructScoper(unscopedProduction, additionalScopeMap, pn); + // } + // return pn; + // } + + // protected void constructScoper( + // Address unscopedProduction, + // Map additionalScopeMap, + // Address production) + // throws PatternMatcherCompileTimeException { + // engine.reteNet.waitForReteTermination(); + // engine.builder.constructScoper(unscopedProduction, additionalScopeMap, production); + // } + + // /** + // * Invalidates the subnet constructed for the recognition of a given + // pattern. + // * The pattern matcher will have to be rebuilt. + // * @param gtPattern the pattern whose matcher subnet should be invalidated + // */ + // public void invalidatePattern(GTPattern gtPattern) { + // Production production = null; + // try { + // production = accessProduction(gtPattern); + // } catch (PatternMatcherCompileTimeException e) { + // // this should not occur here, since we already have a production node + // e.printStackTrace(); + // } + // + // production.tearOff(); + // //production.setDirty(true); + // } + + // updaters for change notification + // if the corresponding rete input isn't created yet, call is ignored + + private static Direction direction(boolean isInsertion) { + return isInsertion ? Direction.INSERT : Direction.DELETE; + } + +// @Override +// public void updateUnary(boolean isInsertion, Object entity, Object typeObject) { +// Address root = inputConnector.getUnaryRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(entity))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectUnarySupertypes(typeObject)) { +// updateUnary(isInsertion, entity, superType); +// } +// } +// } +// +// @Override +// public void updateTernaryEdge(boolean isInsertion, Object relation, Object from, Object to, Object typeObject) { +// Address root = inputConnector.getTernaryEdgeRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(relation), inputConnector.wrapElement(from), +// inputConnector.wrapElement(to))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectTernaryEdgeSupertypes(typeObject)) { +// updateTernaryEdge(isInsertion, relation, from, to, superType); +// } +// } +// } +// @Override +// public void updateBinaryEdge(boolean isInsertion, Object from, Object to, Object typeObject) { +// Address root = inputConnector.getBinaryEdgeRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(from), inputConnector.wrapElement(to))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectBinaryEdgeSupertypes(typeObject)) { +// updateBinaryEdge(isInsertion, from, to, superType); +// } +// } +// } +// +// @Override +// public void updateContainment(boolean isInsertion, Object container, Object element) { +// final Address containmentRoot = inputConnector.getContainmentRoot(); +// if (containmentRoot != null) { +// network.sendExternalUpdate(containmentRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(container), +// inputConnector.wrapElement(element))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } +// +// @Override +// public void updateInstantiation(boolean isInsertion, Object parent, Object child) { +// final Address instantiationRoot = inputConnector.getInstantiationRoot(); +// if (instantiationRoot != null) { +// network.sendExternalUpdate(instantiationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent), +// inputConnector.wrapElement(child))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } +// +// @Override +// public void updateGeneralization(boolean isInsertion, Object parent, Object child) { +// final Address generalizationRoot = inputConnector.getGeneralizationRoot(); +// if (generalizationRoot != null) { +// network.sendExternalUpdate(generalizationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent), +// inputConnector.wrapElement(child))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } + + // no wrapping needed! + public void notifyEvaluator(Address receiver, Tuple tuple) { + network.sendExternalUpdate(receiver, Direction.INSERT, tuple); + if (!engine.isParallelExecutionEnabled()) + network.waitForReteTermination(); + } + + public void mapPlanToAddress(SubPlan plan, Address handle) { + subplanToAddressMapping.put(plan, handle); + } + + public Address getAddress(SubPlan plan) { + return subplanToAddressMapping.get(plan); + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; + +/** + * A problem has occurred during the construction of the RETE net. + * + * @author Gabor Bergmann + * + */ +public class RetePatternBuildException extends QueryProcessingException { + + private static final long serialVersionUID = 6966585498204577548L; + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription) { + super(message, context, shortMessage, patternDescription); + } + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription, + Throwable cause) { + super(message, context, shortMessage, patternDescription, cause); + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.basiclinear; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; + +/** + * Basic layout that builds a linear RETE net based on a heuristic ordering of constraints. + * + * @author Gabor Bergmann + * + */ +public class BasicLinearLayout implements IQueryPlannerStrategy { + + //SubPlanProcessor planProcessor = new SubPlanProcessor(); + + private IQueryBackendHintProvider hintProvider; + private IQueryBackendContext bContext; + /** + * @param bContext + * @since 1.5 + */ + public BasicLinearLayout(IQueryBackendContext bContext) { + this.bContext = bContext; + this.hintProvider = bContext.getHintProvider(); + } + + @Override + public SubPlan plan(final PBody pSystem, Logger logger, IQueryMetaContext context) { + SubPlanFactory planFactory = new SubPlanFactory(pSystem); + PQuery query = pSystem.getPattern(); + //planProcessor.setCompiler(compiler); + try { + logger.debug(String.format( + "%s: patternbody build started for %s", + getClass().getSimpleName(), + query.getFullyQualifiedName())); + + // STARTING THE LINE + SubPlan plan = planFactory.createSubPlan(new PStart()); + + Set pQueue = CollectionsFactory.createSet(pSystem.getConstraints()); + + // MAIN LOOP + while (!pQueue.isEmpty()) { + PConstraint pConstraint = Collections.min(pQueue, + new OrderingHeuristics(plan, context)); // pQueue.iterator().next(); + pQueue.remove(pConstraint); + + // if we have no better option than an unready deferred constraint, raise error + if (pConstraint instanceof DeferredPConstraint) { + final DeferredPConstraint deferred = (DeferredPConstraint) pConstraint; + if (!deferred.isReadyAt(plan, context)) { + raiseForeverDeferredError(deferred, plan, context); + } + } + // TODO integrate the check above in SubPlan / POperation?? + + // replace incumbent plan with its child + plan = planFactory.createSubPlan(new PApply(pConstraint), plan); + } + + // PROJECT TO PARAMETERS + SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), plan); + + // FINAL CHECK, whether all exported variables are present + all constraint satisfied + BuildHelper.finalCheck(pSystem, finalPlan, context); + // TODO integrate the check above in SubPlan / POperation + + logger.debug(String.format( + "%s: patternbody query plan concluded for %s as: %s", + getClass().getSimpleName(), + query.getFullyQualifiedName(), + finalPlan.toLongString())); + + return finalPlan; + + } catch (RetePatternBuildException ex) { + ex.setPatternDescription(query); + throw ex; + } + } + + /** + * Called when the constraint is not ready, but cannot be deferred further. + * + * @param plan + * @throws RetePatternBuildException + * to indicate the error in detail. + */ + private void raiseForeverDeferredError(DeferredPConstraint constraint, SubPlan plan, IQueryMetaContext context) { + if (constraint instanceof Equality) { + raiseForeverDeferredError((Equality)constraint, plan, context); + } else if (constraint instanceof ExportedParameter) { + raiseForeverDeferredError((ExportedParameter)constraint, plan, context); + } else if (constraint instanceof ExpressionEvaluation) { + raiseForeverDeferredError((ExpressionEvaluation)constraint, plan, context); + } else if (constraint instanceof VariableDeferredPConstraint) { + raiseForeverDeferredError(constraint, plan, context); + } + } + + private void raiseForeverDeferredError(Equality constraint, SubPlan plan, IQueryMetaContext context) { + String[] args = { constraint.getWho().toString(), constraint.getWithWhom().toString() }; + String msg = "Cannot express equality of variables {1} and {2} if neither of them is deducable."; + String shortMsg = "Equality between undeducible variables."; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + private void raiseForeverDeferredError(ExportedParameter constraint, SubPlan plan, IQueryMetaContext context) { + String[] args = { constraint.getParameterName() }; + String msg = "Pattern Graph Search terminated incompletely: " + + "exported pattern variable {1} could not be determined based on the pattern constraints. " + + "HINT: certain constructs (e.g. negative patterns or check expressions) cannot output symbolic parameters."; + String shortMsg = "Could not deduce value of parameter"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + private void raiseForeverDeferredError(ExpressionEvaluation constraint, SubPlan plan, IQueryMetaContext context) { + if (constraint.checkTypeSafety(plan, context) == null) { + raiseForeverDeferredError(constraint, plan); + } else { + String[] args = { toString(), constraint.checkTypeSafety(plan, context).toString() }; + String msg = "The checking of pattern constraint {1} cannot be deferred further, but variable {2} is still not type safe. " + + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible."; + String shortMsg = "Could not check all constraints due to undeducible type restrictions"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + } + private void raiseForeverDeferredError(VariableDeferredPConstraint constraint, SubPlan plan) { + Set missing = CollectionsFactory.createSet(constraint.getDeferringVariables());//new HashSet(getDeferringVariables()); + missing.removeAll(plan.getVisibleVariables()); + String[] args = { toString(), Arrays.toString(missing.toArray()) }; + String msg = "The checking of pattern constraint {1} requires the values of variables {2}, but it cannot be deferred further. " + + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible."; + String shortMsg = "Could not check all constraints due to undeducible variables"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.basiclinear; + +import java.util.Comparator; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent; + +/** + * @author Gabor Bergmann + * + */ +public class OrderingHeuristics implements Comparator { + private SubPlan plan; + private IQueryMetaContext context; + + public OrderingHeuristics(SubPlan plan, IQueryMetaContext context) { + super(); + this.plan = plan; + this.context = context; + } + + @Override + public int compare(PConstraint o1, PConstraint o2) { + return new OrderingCompareAgent(o1, o2) { + @Override + protected void doCompare() { + boolean temp = consider(preferTrue(isConstant(a), isConstant(b))) + && consider(preferTrue(isReady(a), isReady(b))); + if (!temp) + return; + + Set bound1 = boundVariables(a); + Set bound2 = boundVariables(b); + swallowBoolean(temp && consider(preferTrue(isBound(a, bound1), isBound(b, bound2))) + && consider(preferMore(degreeBound(a, bound1), degreeBound(b, bound2))) + && consider(preferLess(degreeFree(a, bound1), degreeFree(b, bound2))) + + // tie breaking + && consider(preferLess(a.getMonotonousID(), b.getMonotonousID())) // this is hopefully deterministic + && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b)))); + } + }.compare(); + } + + boolean isConstant(PConstraint o) { + return (o instanceof ConstantValue); + } + + boolean isReady(PConstraint o) { + return (o instanceof EnumerablePConstraint) + || (o instanceof DeferredPConstraint && ((DeferredPConstraint) o) + .isReadyAt(plan, context)); + } + + Set boundVariables(PConstraint o) { + Set boundVariables = CollectionsFactory.createSet(o.getAffectedVariables()); + boundVariables.retainAll(plan.getVisibleVariables()); + return boundVariables; + } + + boolean isBound(PConstraint o, Set boundVariables) { + return boundVariables.size() == o.getAffectedVariables().size(); + } + + int degreeBound(PConstraint o, Set boundVariables) { + return boundVariables.size(); + } + + int degreeFree(PConstraint o, Set boundVariables) { + return o.getAffectedVariables().size() - boundVariables.size(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.EqualityFilterRecipe; +import tools.refinery.viatra.runtime.rete.recipes.IndexerBasedAggregatorRecipe; +import tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.JoinRecipe; +import tools.refinery.viatra.runtime.rete.recipes.Mask; +import tools.refinery.viatra.runtime.rete.recipes.MonotonicityInfo; +import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe; +import tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.RecipesFactory; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.recipes.SingleColumnAggregatorRecipe; +import tools.refinery.viatra.runtime.rete.recipes.TrimmerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.CompiledSubPlan; +import tools.refinery.viatra.runtime.rete.traceability.PlanningTrace; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +/** + * @author Bergmann Gabor + * + */ +public class CompilerHelper { + + private CompilerHelper() {/*Utility class constructor*/} + + static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; + + /** + * Makes sure that all variables in the tuple are different so that it can be used as {@link CompiledSubPlan}. If a + * variable occurs multiple times, equality checks are applied and then the results are trimmed so that duplicates + * are hidden. If no manipulation is necessary, the original trace is returned. + * + *

+ * to be used whenever a constraint introduces new variables. + */ + public static PlanningTrace checkAndTrimEqualVariables(SubPlan plan, final PlanningTrace coreTrace) { + // are variables in the constraint all different? + final List coreVariablesTuple = coreTrace.getVariablesTuple(); + final int constraintArity = coreVariablesTuple.size(); + final int distinctVariables = coreTrace.getPosMapping().size(); + if (constraintArity == distinctVariables) { + // all variables occur exactly once in tuple + return coreTrace; + } else { // apply equality checks and trim + + // find the positions in the tuple for each variable + Map> posMultimap = new HashMap>(); + List trimmedVariablesTuple = new ArrayList(distinctVariables); + int[] trimIndices = new int[distinctVariables]; + for (int i = 0; i < constraintArity; ++i) { + final PVariable variable = coreVariablesTuple.get(i); + SortedSet indexSet = posMultimap.get(variable); + if (indexSet == null) { // first occurrence of variable + indexSet = new TreeSet(); + posMultimap.put(variable, indexSet); + + // this is the first occurrence, set up trimming + trimIndices[trimmedVariablesTuple.size()] = i; + trimmedVariablesTuple.add(variable); + } + indexSet.add(i); + } + + // construct equality checks for each variable occurring multiple times + PlanningTrace lastTrace = coreTrace; + for (Entry> entry : posMultimap.entrySet()) { + if (entry.getValue().size() > 1) { + EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); + equalityFilterRecipe.setParent(lastTrace.getRecipe()); + equalityFilterRecipe.getIndices().addAll(entry.getValue()); + lastTrace = new PlanningTrace(plan, coreVariablesTuple, equalityFilterRecipe, lastTrace); + } + } + + // trim so that each variable occurs only once + TrimmerRecipe trimmerRecipe = FACTORY.createTrimmerRecipe(); + trimmerRecipe.setParent(lastTrace.getRecipe()); + trimmerRecipe.setMask(tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper + .mask(constraintArity, trimIndices)); + return new PlanningTrace(plan, trimmedVariablesTuple, trimmerRecipe, lastTrace); + } + } + + /** + * Extracts the variable list representation of the variables tuple. + */ + public static List convertVariablesTuple(EnumerablePConstraint constraint) { + return convertVariablesTuple(constraint.getVariablesTuple()); + } + + /** + * Extracts the variable list representation of the variables tuple. + */ + public static List convertVariablesTuple(Tuple variablesTuple) { + List result = new ArrayList(); + for (Object o : variablesTuple.getElements()) + result.add((PVariable) o); + return result; + } + + /** + * Returns a compiled indexer trace according to a mask + */ + public static RecipeTraceInfo makeIndexerTrace(SubPlan planToCompile, PlanningTrace parentTrace, TupleMask mask) { + final ReteNodeRecipe parentRecipe = parentTrace.getRecipe(); + if (parentRecipe instanceof IndexerBasedAggregatorRecipe + || parentRecipe instanceof SingleColumnAggregatorRecipe) + throw new IllegalArgumentException( + "Cannot take projection indexer of aggregator node at plan " + planToCompile); + IndexerRecipe recipe = RecipesHelper.projectionIndexerRecipe(parentRecipe, toRecipeMask(mask)); + // final List maskedVariables = mask.transform(parentTrace.getVariablesTuple()); + return new PlanningTrace(planToCompile, /* maskedVariables */ parentTrace.getVariablesTuple(), recipe, + parentTrace); + // TODO add specialized indexer trace info? + } + + /** + * Creates a trimmer that keeps selected variables only. + */ + protected static TrimmerRecipe makeTrimmerRecipe(final PlanningTrace compiledParent, + List projectedVariables) { + final Mask projectionMask = makeProjectionMask(compiledParent, projectedVariables); + final TrimmerRecipe trimmerRecipe = ReteRecipeCompiler.FACTORY.createTrimmerRecipe(); + trimmerRecipe.setParent(compiledParent.getRecipe()); + trimmerRecipe.setMask(projectionMask); + return trimmerRecipe; + } + + public static Mask makeProjectionMask(final PlanningTrace compiledParent, Iterable projectedVariables) { + List projectionSourceIndices = new ArrayList(); + for (PVariable pVariable : projectedVariables) { + projectionSourceIndices.add(compiledParent.getPosMapping().get(pVariable)); + } + final Mask projectionMask = RecipesHelper.mask(compiledParent.getRecipe().getArity(), projectionSourceIndices); + return projectionMask; + } + + /** + * @since 1.6 + */ + public static final class PosetTriplet { + public Mask coreMask; + public Mask posetMask; + public IPosetComparator comparator; + } + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(List variables, PBody body, IQueryMetaContext context) { + Map> typeMap = TypeHelper.inferUnaryTypesFor(variables, body.getConstraints(), + context); + List> keys = new LinkedList>(); + + for (int i = 0; i < variables.size(); i++) { + keys.add(typeMap.get(variables.get(i))); + } + + return computePosetInfo(keys, context); + } + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(List parameters, IQueryMetaContext context) { + List> keys = new LinkedList>(); + for (int i = 0; i < parameters.size(); i++) { + IInputKey key = parameters.get(i).getDeclaredUnaryType(); + if (key == null) { + keys.add(Collections.emptySet()); + } else { + keys.add(Collections.singleton(parameters.get(i).getDeclaredUnaryType())); + } + } + return computePosetInfo(keys, context); + } + + + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(Iterable> keys, IQueryMetaContext context) { + PosetTriplet result = new PosetTriplet(); + List coreIndices = new ArrayList(); + List posetIndices = new ArrayList(); + List filtered = new ArrayList(); + boolean posetKey = false; + int index = -1; + + for (Set _keys : keys) { + ++index; + posetKey = false; + + for (IInputKey key : _keys) { + if (key != null && context.isPosetKey(key)) { + posetKey = true; + filtered.add(key); + break; + } + } + + if (posetKey) { + posetIndices.add(index); + } else { + coreIndices.add(index); + } + } + + result.comparator = context.getPosetComparator(filtered); + result.coreMask = RecipesHelper.mask(index + 1, coreIndices); + result.posetMask = RecipesHelper.mask(index + 1, posetIndices); + + return result; + } + + /** + * Creates a recipe for a production node and the corresponding trace. + *

PRE: in case this is a recursion cutoff point (see {@link RecursionCutoffPoint}) + * and bodyFinalTraces will be filled later, + * the object yielded now by bodyFinalTraces.values() must return up-to-date results later + * @since 2.4 + */ + public static CompiledQuery makeQueryTrace(PQuery query, Map bodyFinalTraces, + Collection bodyFinalRecipes, QueryEvaluationHint hint, IQueryMetaContext context, + boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + ProductionRecipe recipe = ReteRecipeCompiler.FACTORY.createProductionRecipe(); + + // temporary solution to support the deprecated option for now + boolean deleteAndRederiveEvaluationDep = deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(hint); + + recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + + if (deleteAndRederiveEvaluationDep || (timelyEvaluation != null)) { + PosetTriplet triplet = computePosetInfo(query.getParameters(), context); + if (triplet.comparator != null) { + MonotonicityInfo info = FACTORY.createMonotonicityInfo(); + info.setCoreMask(triplet.coreMask); + info.setPosetMask(triplet.posetMask); + info.setPosetComparator(triplet.comparator); + recipe.setOptionalMonotonicityInfo(info); + } + } + + recipe.setPattern(query); + recipe.setPatternFQN(query.getFullyQualifiedName()); + recipe.setTraceInfo(recipe.getPatternFQN()); + recipe.getParents().addAll(bodyFinalRecipes); + for (int i = 0; i < query.getParameterNames().size(); ++i) { + recipe.getMappedIndices().put(query.getParameterNames().get(i), i); + } + + return new CompiledQuery(recipe, bodyFinalTraces, query); + } + + /** + * Calculated index mappings for a join, based on the common variables of the two parent subplans. + * + * @author Gabor Bergmann + * + */ + public static class JoinHelper { + private TupleMask primaryMask; + private TupleMask secondaryMask; + private TupleMask complementerMask; + private RecipeTraceInfo primaryIndexer; + private RecipeTraceInfo secondaryIndexer; + private JoinRecipe naturalJoinRecipe; + private List naturalJoinVariablesTuple; + + /** + * @pre enforceVariableCoincidences() has been called on both sides. + */ + public JoinHelper(SubPlan planToCompile, PlanningTrace primaryCompiled, PlanningTrace callTrace) { + super(); + + Set primaryVariables = new LinkedHashSet(primaryCompiled.getVariablesTuple()); + Set secondaryVariables = new LinkedHashSet(callTrace.getVariablesTuple()); + int oldNodes = 0; + Set introducingSecondaryIndices = new TreeSet(); + for (PVariable var : secondaryVariables) { + if (primaryVariables.contains(var)) + oldNodes++; + else + introducingSecondaryIndices.add(callTrace.getPosMapping().get(var)); + } + List primaryIndices = new ArrayList(oldNodes); + List secondaryIndices = new ArrayList(oldNodes); + for (PVariable var : secondaryVariables) { + if (primaryVariables.contains(var)) { + primaryIndices.add(primaryCompiled.getPosMapping().get(var)); + secondaryIndices.add(callTrace.getPosMapping().get(var)); + } + } + Collection complementerIndices = introducingSecondaryIndices; + + primaryMask = TupleMask.fromSelectedIndices(primaryCompiled.getVariablesTuple().size(), primaryIndices); + secondaryMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), secondaryIndices); + complementerMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), complementerIndices); + + primaryIndexer = makeIndexerTrace(planToCompile, primaryCompiled, primaryMask); + secondaryIndexer = makeIndexerTrace(planToCompile, callTrace, secondaryMask); + + naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParentComplementaryMask(CompilerHelper.toRecipeMask(complementerMask)); + + naturalJoinVariablesTuple = new ArrayList(primaryCompiled.getVariablesTuple()); + for (int complementerIndex : complementerMask.indices) + naturalJoinVariablesTuple.add(callTrace.getVariablesTuple().get(complementerIndex)); + } + + public TupleMask getPrimaryMask() { + return primaryMask; + } + + public TupleMask getSecondaryMask() { + return secondaryMask; + } + + public TupleMask getComplementerMask() { + return complementerMask; + } + + public RecipeTraceInfo getPrimaryIndexer() { + return primaryIndexer; + } + + public RecipeTraceInfo getSecondaryIndexer() { + return secondaryIndexer; + } + + public JoinRecipe getNaturalJoinRecipe() { + return naturalJoinRecipe; + } + + public List getNaturalJoinVariablesTuple() { + return naturalJoinVariablesTuple; + } + + } + + /** + * @since 1.4 + */ + public static Mask toRecipeMask(TupleMask mask) { + return RecipesHelper.mask(mask.sourceWidth, mask.indices); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * In a recursive query structure, query composition references can be cut off so that the remaining structure is DAG. + * {@link RecursionCutoffPoint} represents one such cut off query composition. + * When the compilation of the recursive query finishes and the compiled form becomes available, + * the {@link RecursionCutoffPoint} has to be signaled to update parent traces and recipes of the recursive call. + * + * @author Bergmann Gabor + * @noreference This class is not intended to be referenced by clients + * + */ +public class RecursionCutoffPoint { + final Map futureTraceMap; + final CompiledQuery compiledQuery; + final ProductionRecipe recipe; + final QueryEvaluationHint hint; + + public RecursionCutoffPoint(PQuery query, QueryEvaluationHint hint, IQueryMetaContext context, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + super(); + this.hint = hint; + this.futureTraceMap = new HashMap<>(); // IMPORTANT: the identity of futureTraceMap.values() will not change + this.compiledQuery = CompilerHelper.makeQueryTrace(query, futureTraceMap, Collections.emptySet(), hint, context, deleteAndRederiveEvaluation, timelyEvaluation); + this.recipe = (ProductionRecipe)compiledQuery.getRecipe(); + if (!compiledQuery.getParentRecipeTraces().isEmpty()) { + throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has trace parents: %s", + compiledQuery.getQuery(), + prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces()))); + } + if (!recipe.getParents().isEmpty()) { + throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has recipe parents: %s", + compiledQuery.getQuery(), + prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces()))); + } + } + + + + private String prettyPrintParentRecipeTraces(List trace) { + return trace.stream().map(Object::toString).collect(Collectors.joining(", ")); + } + + /** + * Signals that compilation of the recursive query has terminated, culminating into the given compiled form. + * The query composition that has been cut off will be connected now. + */ + public void mend(CompiledQuery finalCompiledForm) { + futureTraceMap.putAll(finalCompiledForm.getParentRecipeTracesPerBody()); + recipe.getParents().addAll(((ProductionRecipe)finalCompiledForm.getRecipe()).getParents()); + } + + public CompiledQuery getCompiledQuery() { + return compiledQuery; + } + + public ProductionRecipe getRecipe() { + return recipe; + } + + + +} 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..6dd270bd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java @@ -0,0 +1,948 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.*; +import tools.refinery.viatra.runtime.matchers.psystem.*; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.*; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.JoinHelper; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.PosetTriplet; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.traceability.*; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Compiles queries and query plans into Rete recipes, traced by respectively a {@link CompiledQuery} or + * {@link CompiledSubPlan}. + * + * @author Bergmann Gabor + * + */ +public class ReteRecipeCompiler { + + private final IQueryPlannerStrategy plannerStrategy; + private final IQueryMetaContext metaContext; + private final IQueryBackendHintProvider hintProvider; + private final PDisjunctionRewriter normalizer; + private final QueryAnalyzer queryAnalyzer; + private final Logger logger; + + /** + * @since 2.2 + */ + protected final boolean deleteAndRederiveEvaluation; + /** + * @since 2.4 + */ + protected final TimelyConfiguration timelyEvaluation; + + /** + * @since 1.5 + */ + public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, + IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer) { + this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null); + } + + /** + * @since 2.4 + */ + public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, + IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer, + boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + super(); + this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; + this.timelyEvaluation = timelyEvaluation; + this.plannerStrategy = plannerStrategy; + this.logger = logger; + this.metaContext = metaContext; + this.queryAnalyzer = queryAnalyzer; + this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(), + new PBodyNormalizer(metaContext) { + + @Override + protected boolean shouldExpandWeakenedAlternatives(PQuery query) { + QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query); + Boolean expandWeakenedAlternativeConstraints = ReteHintOptions.expandWeakenedAlternativeConstraints + .getValueOrDefault(hint); + return expandWeakenedAlternativeConstraints; + } + + }); + this.hintProvider = hintProvider; + } + + static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; + + // INTERNALLY CACHED + private Map plannerCache = new HashMap(); + private Set planningInProgress = new HashSet(); + + private Map queryCompilerCache = new HashMap(); + private Set compilationInProgress = new HashSet(); + private IMultiLookup recursionCutoffPoints = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private Map subPlanCompilerCache = new HashMap(); + private Map compilerBackTrace = new HashMap(); + + /** + * Clears internal state + */ + public void reset() { + plannerCache.clear(); + planningInProgress.clear(); + queryCompilerCache.clear(); + subPlanCompilerCache.clear(); + compilerBackTrace.clear(); + } + + /** + * Returns a {@link CompiledQuery} compiled from a query + * @throws ViatraQueryRuntimeException + */ + public CompiledQuery getCompiledForm(PQuery query) { + CompiledQuery compiled = queryCompilerCache.get(query); + if (compiled == null) { + + IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector + .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)); + if (traceCollector != null) { + traceCollector.addTrace(query, query); + } + + boolean reentrant = !compilationInProgress.add(query); + if (reentrant) { // oops, recursion into body in progress + RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext, + deleteAndRederiveEvaluation, timelyEvaluation); + recursionCutoffPoints.addPair(query, cutoffPoint); + return cutoffPoint.getCompiledQuery(); + } else { // not reentrant, therefore no recursion, do the compilation + try { + compiled = compileProduction(query); + queryCompilerCache.put(query, compiled); + // backTrace.put(compiled.getRecipe(), plan); + + // if this was a recursive query, mend all points where recursion was cut off + for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) { + cutoffPoint.mend(compiled); + } + } finally { + compilationInProgress.remove(query); + } + } + } + return compiled; + } + + /** + * Returns a {@link CompiledSubPlan} compiled from a query plan + * @throws ViatraQueryRuntimeException + */ + public CompiledSubPlan getCompiledForm(SubPlan plan) { + CompiledSubPlan compiled = subPlanCompilerCache.get(plan); + if (compiled == null) { + compiled = doCompileDispatch(plan); + subPlanCompilerCache.put(plan, compiled); + compilerBackTrace.put(compiled.getRecipe(), plan); + } + return compiled; + } + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan getPlan(PBody pBody) { + // if the query is not marked as being compiled, initiate compilation + // (this is useful in case of recursion if getPlan() is the entry point) + PQuery pQuery = pBody.getPattern(); + if (!compilationInProgress.contains(pQuery)) + getCompiledForm(pQuery); + + // Is the plan already cached? + SubPlan plan = plannerCache.get(pBody); + if (plan == null) { + boolean reentrant = !planningInProgress.add(pBody); + if (reentrant) { // oops, recursion into body in progress + throw new IllegalArgumentException( + "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName()); + } else { // not reentrant, therefore no recursion, do the planning + try { + plan = plannerStrategy.plan(pBody, logger, metaContext); + plannerCache.put(pBody, plan); + } finally { + planningInProgress.remove(pBody); + } + } + } + return plan; + } + + private CompiledQuery compileProduction(PQuery query) { + Collection bodyPlans = new ArrayList(); + normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector + .getValueOrDefault(hintProvider.getQueryEvaluationHint(query))); + for (PBody pBody : normalizer.rewrite(query).getBodies()) { + SubPlan bodyPlan = getPlan(pBody); + bodyPlans.add(bodyPlan); + } + return doCompileProduction(query, bodyPlans); + } + + private CompiledQuery doCompileProduction(PQuery query, Collection bodies) { + // TODO skip production node if there is just one body and no projection needed? + Map bodyFinalTraces = new HashMap(); + Collection bodyFinalRecipes = new HashSet(); + + for (SubPlan bodyFinalPlan : bodies) { + // skip over any projections at the end + bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); + + // TODO checkAndTrimEqualVariables may introduce superfluous trim, + // but whatever (no uniqueness enforcer needed) + + // compile body + final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); + + // project to parameter list + RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false); + + bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace); + bodyFinalRecipes.add(finalTrace.getRecipe()); + } + + CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes, + getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation); + + return compiled; + } + + private CompiledSubPlan doCompileDispatch(SubPlan plan) { + final POperation operation = plan.getOperation(); + if (operation instanceof PEnumerate) { + return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan); + } else if (operation instanceof PApply) { + final PConstraint pConstraint = ((PApply) operation).getPConstraint(); + if (pConstraint instanceof EnumerablePConstraint) { + CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0)); + PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint); + return compileToNaturalJoin(plan, primaryParent, secondaryParent); + } else if (pConstraint instanceof DeferredPConstraint) { + return doDeferredDispatch((DeferredPConstraint) pConstraint, plan); + } else { + throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString()); + } + } else if (operation instanceof PJoin) { + return doCompileJoin((PJoin) operation, plan); + } else if (operation instanceof PProject) { + return doCompileProject((PProject) operation, plan); + } else if (operation instanceof PStart) { + return doCompileStart((PStart) operation, plan); + } else { + throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString()); + } + } + + private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) { + final SubPlan parentPlan = plan.getParentPlans().get(0); + final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan); + if (constraint instanceof Equality) { + return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof ExportedParameter) { + return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof Inequality) { + return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof NegativePatternCall) { + return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof PatternMatchCounter) { + return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof AggregatorConstraint) { + return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof ExpressionEvaluation) { + return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof TypeFilterConstraint) { + return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled); + } + throw new UnsupportedOperationException("Unknown deferred constraint " + constraint); + } + + private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + if (constraint.isMoot()) + return parentCompiled.cloneFor(plan); + + Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); + Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); + + if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { + Integer indexLower = Math.min(index1, index2); + Integer indexHigher = Math.max(index1, index2); + + EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); + equalityFilterRecipe.setParent(parentCompiled.getRecipe()); + equalityFilterRecipe.getIndices().add(indexLower); + equalityFilterRecipe.getIndices().add(indexHigher); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled); + } else { + throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", + plan.toShortString(), parentCompiled.toString())); + } + } + + /** + * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in + * toFilterTrace. + */ + private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace, + ConstantRecipe constantRecipe, List filteredVariables) { + PlanningTrace resultTrace = toFilterTrace; + + int constantVariablesSize = filteredVariables.size(); + for (int i = 0; i < constantVariablesSize; ++i) { + Object constantValue = constantRecipe.getConstantValues().get(i); + PVariable filteredVariable = filteredVariables.get(i); + int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable); + + DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe(); + dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn); + dispatcherRecipe.setParent(resultTrace.getRecipe()); + + PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe, + resultTrace); + + DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe(); + bucketRecipe.setBucketKey(constantValue); + bucketRecipe.setParent(dispatcherRecipe); + + PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe, + dispatcherTrace); + + resultTrace = bucketTrace; + } + + return resultTrace.cloneFor(plan); + } + + private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + return parentCompiled.cloneFor(plan); + } + + private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + if (constraint.isEliminable()) + return parentCompiled.cloneFor(plan); + + Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); + Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); + + if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { + Integer indexLower = Math.min(index1, index2); + Integer indexHigher = Math.max(index1, index2); + + InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe(); + inequalityFilterRecipe.setParent(parentCompiled.getRecipe()); + inequalityFilterRecipe.setSubject(indexLower); + inequalityFilterRecipe.getInequals().add(indexHigher); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe, + parentCompiled); + } else { + throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", + plan.toShortString(), parentCompiled.toString())); + } + } + + private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final IInputKey inputKey = constraint.getInputKey(); + if (!metaContext.isStateless(inputKey)) + throw new UnsupportedOperationException( + "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike " + + inputKey); + + final Tuple constraintVariables = constraint.getVariablesTuple(); + final List parentVariables = parentCompiled.getVariablesTuple(); + + Mask mask; // select elements of the tuple to check against extensional relation + if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) { + mask = null; // lucky case, parent signature equals that of input key + } else { + List variables = new ArrayList(); + for (Object variable : constraintVariables.getElements()) { + variables.add((PVariable) variable); + } + mask = CompilerHelper.makeProjectionMask(parentCompiled, variables); + } + InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey, + inputKey.getStringID(), mask); + return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled); + } + + private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + JoinHelper joinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer(); + final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer(); + + AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe(); + antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer, + secondaryIndexer); + } + + private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + // hack: use some mask computations (+ the indexers) from a fake natural join against the called query + JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); + final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer(); + + final List sideVariablesTuple = new ArrayList( + fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple())); + /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); + + CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe(); + aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe()); + PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, + callProjectionIndexer); + + IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); + aggregatorIndexerRecipe.setParent(aggregatorRecipe); + // aggregatorIndexerRecipe.setMask(RecipesHelper.mask( + // sideVariablesTuple.size(), + // //use same indices as in the projection indexer + // // EVEN if result variable already visible in left parent + // fakeJoinHelper.getSecondaryMask().indices + // )); + + int aggregatorWidth = sideVariablesTuple.size(); + int aggregateResultIndex = aggregatorWidth - 1; + + aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( + // aggregate according all but the last index + aggregateResultIndex, aggregatorWidth))); + PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, + aggregatorTrace); + + JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); + naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, + // extend with last element only - the computation value + aggregateResultIndex)); + + // what if the new variable already has a value? + // even if already known, we add the new result variable, so that it can be filtered at the end + // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); + + final List aggregatedVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + aggregatedVariablesTuple.add(constraint.getResultVariable()); + + PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer, + aggregatorIndexerTrace); + + return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); + // if (!alreadyKnown) { + // return joinTrace.cloneFor(plan); + // } else { + // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); + // } + } + + private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + // hack: use some mask computations (+ the indexers) from a fake natural join against the called query + JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); + TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask(); + + final List sideVariablesTuple = new ArrayList( + callGroupMask.transform(callTrace.getVariablesTuple())); + /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); + + IMultisetAggregationOperator operator = constraint.getAggregator().getOperator(); + + SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe(); + columnAggregatorRecipe.setParent(callTrace.getRecipe()); + columnAggregatorRecipe.setMultisetAggregationOperator(operator); + + int columnIndex = constraint.getAggregatedColumn(); + IPosetComparator posetComparator = null; + Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask); + + // temporary solution to support the deprecated option for now + final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan)); + + columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { + List parameters = constraint.getReferredQuery().getParameters(); + IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType(); + if (key != null && metaContext.isPosetKey(key)) { + posetComparator = metaContext.getPosetComparator(Collections.singleton(key)); + } + } + + if (posetComparator == null) { + columnAggregatorRecipe.setGroupByMask(groupMask); + columnAggregatorRecipe.setAggregableIndex(columnIndex); + } else { + MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo(); + monotonicityInfo.setCoreMask(groupMask); + monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask( + TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize()))); + monotonicityInfo.setPosetComparator(posetComparator); + columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo); + } + + ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe; + PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace); + + IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); + aggregatorIndexerRecipe.setParent(aggregatorRecipe); + + int aggregatorWidth = sideVariablesTuple.size(); + int aggregateResultIndex = aggregatorWidth - 1; + + aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( + // aggregate according all but the last index + aggregateResultIndex, aggregatorWidth))); + PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, + aggregatorTrace); + + JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); + naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, + // extend with last element only - the computation value + aggregateResultIndex)); + + // what if the new variable already has a value? + // even if already known, we add the new result variable, so that it can be filtered at the end + // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); + + final List finalVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + finalVariablesTuple.add(constraint.getResultVariable()); + + PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer, + aggregatorIndexerTrace); + + return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); + // if (!alreadyKnown) { + // return joinTrace.cloneFor(plan); + // } else { + // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); + // } + } + + private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + Map tupleNameMap = new HashMap(); + for (String name : constraint.getEvaluator().getInputParameterNames()) { + Map index = parentCompiled.getPosMapping(); + PVariable variable = constraint.getPSystem().getVariableByNameChecked(name); + Integer position = index.get(variable); + tupleNameMap.put(name, position); + } + + final PVariable outputVariable = constraint.getOutputVariable(); + final boolean booleanCheck = outputVariable == null; + + // TODO determine whether expression is costly + boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan)); + // for (PAnnotation pAnnotation : + // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) { + // for (Object value : pAnnotation.getAllValues("expensive")) { + // if (value instanceof Boolean) + // cacheOutput = (boolean) value; + // } + // } + + ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe() + : FACTORY.createEvalRecipe(); + enforcerRecipe.setParent(parentCompiled.getRecipe()); + enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator())); + enforcerRecipe.setCacheOutput(cacheOutput); + if (enforcerRecipe instanceof EvalRecipe) { + ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding()); + } + for (Entry entry : tupleNameMap.entrySet()) { + enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue()); + } + + final List enforcerVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + if (!booleanCheck) + enforcerVariablesTuple.add(outputVariable); + PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled); + + return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan); + } + + private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) { + final List compiledParents = getCompiledFormOfParents(plan); + final CompiledSubPlan leftCompiled = compiledParents.get(0); + final CompiledSubPlan rightCompiled = compiledParents.get(1); + + return compileToNaturalJoin(plan, leftCompiled, rightCompiled); + } + + private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled, + final PlanningTrace rightCompiled) { + // CHECK IF SPECIAL CASE + + // Is constant filtering applicable? + if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) { + if (leftCompiled.getRecipe() instanceof ConstantRecipe + && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) { + return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(), + leftCompiled.getVariablesTuple()); + } + if (rightCompiled.getRecipe() instanceof ConstantRecipe + && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) { + return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(), + rightCompiled.getVariablesTuple()); + } + } + + // ELSE: ACTUAL JOIN + JoinHelper joinHelper = new JoinHelper(plan, leftCompiled, rightCompiled); + return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(), + joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer()); + } + + private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) { + final List compiledParents = getCompiledFormOfParents(plan); + final CompiledSubPlan compiledParent = compiledParents.get(0); + + List projectedVariables = new ArrayList(operation.getToVariables()); + // Determinizing projection: try to keep original order (hopefully facilitates node reuse) + Map parentPosMapping = compiledParent.getPosMapping(); + Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get)); + + return doProjectPlan(compiledParent, projectedVariables, true, + parentTrace -> parentTrace.cloneFor(plan), + (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace), + (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace) + ); + } + + /** + * Projects a subplan onto the specified variable tuple + * @param compiledParentPlan the compiled form of the subplan + * @param targetVariables list of variables to project to + * @param enforceUniqueness whether distinctness shall be enforced after the projection. + * Specify false only if directly connecting to a production node. + * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan, in case it is sufficient + * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the node and its parent trace + * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the node and its parent trace + * @since 2.1 + */ + ResultTrace doProjectPlan( + final CompiledSubPlan compiledParentPlan, + final List targetVariables, + boolean enforceUniqueness, + Function reinterpretTraceFactory, + BiFunction intermediateTraceFactory, + BiFunction finalTraceFactory) + { + if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed + return reinterpretTraceFactory.apply(compiledParentPlan); + + // otherwise, we need at least a trimmer + TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables); + + // do we need to eliminate duplicates? + SubPlan parentPlan = compiledParentPlan.getSubPlan(); + if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined( + parentPlan, + targetVariables, + queryAnalyzer, + true)) + { + // if uniqueness enforcess is unwanted or unneeeded, skip it + return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan); + } else { + // add a uniqueness enforcer + UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe(); + recipe.getParents().add(trimmerRecipe); + + // temporary solution to support the deprecated option for now + final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan)); + + recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { + PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables, parentPlan.getBody(), metaContext); + + if (triplet.comparator != null) { + MonotonicityInfo info = FACTORY.createMonotonicityInfo(); + info.setCoreMask(triplet.coreMask); + info.setPosetMask(triplet.posetMask); + info.setPosetComparator(triplet.comparator); + recipe.setOptionalMonotonicityInfo(info); + } + } + + RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan); + return finalTraceFactory.apply(recipe, trimmerTrace); + } + } + + /** + * Projects the final compiled form of a PBody onto the parameter tuple + * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to query parameters + * @param enforceUniqueness whether distinctness shall be enforced after the projection. + * Specify false only if directly connecting to a production node. + * @since 2.1 + */ + RecipeTraceInfo projectBodyFinalToParameters( + final CompiledSubPlan compiledBody, + boolean enforceUniqueness) + { + final PBody body = compiledBody.getSubPlan().getBody(); + final List parameterList = body.getSymbolicParameterVariables(); + + return doProjectPlan(compiledBody, parameterList, enforceUniqueness, + parentTrace -> parentTrace, + (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace), + (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace) + ); + } + + private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) { + if (!operation.getAPrioriVariables().isEmpty()) { + throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString()); + } + final ConstantRecipe recipe = FACTORY.createConstantRecipe(); + recipe.getConstantValues().clear(); + + return new CompiledSubPlan(plan, new ArrayList(), recipe); + } + + private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) { + final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan); + + return trimmedTrace.cloneFor(plan); + } + + private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) { + final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint); + final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace); + return trimmedTrace; + } + + private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) { + if (constraint instanceof RelationEvaluation) { + return compileEnumerable(plan, (RelationEvaluation) constraint); + } else if (constraint instanceof BinaryTransitiveClosure) { + return compileEnumerable(plan, (BinaryTransitiveClosure) constraint); + } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { + return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint); + } else if (constraint instanceof RepresentativeElectionConstraint) { + return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint); + } else if (constraint instanceof ConstantValue) { + return compileEnumerable(plan, (ConstantValue) constraint); + } else if (constraint instanceof PositivePatternCall) { + return compileEnumerable(plan, (PositivePatternCall) constraint); + } else if (constraint instanceof TypeConstraint) { + return compileEnumerable(plan, (TypeConstraint) constraint); + } + throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint); + } + + private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) { + // TODO the implementation would perform better if an inequality check would be used after tcRecipe and + // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not available + // in recipe metamodel in VIATRA 2.0 + + // Find called query + final PQuery referredQuery = constraint.getSupplierKey(); + final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + + // Calculate irreflexive transitive closure + final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe(); + tcRecipe.setParent(callTrace.getRecipe()); + final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), tcRecipe, callTrace); + + // Enumerate universe type + final IInputKey inputKey = constraint.getUniverseType(); + final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); + final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple( + Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe); + + // Calculate reflexive access by duplicating universe type column + final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe(); + reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0)); + reflexiveRecipe.setParent(universeTypeRecipe); + final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), reflexiveRecipe, universeTypeTrace); + + // Finally, reduce duplicates after a join + final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe(); + brtcRecipe.getParents().add(tcRecipe); + brtcRecipe.getParents().add(reflexiveRecipe); + + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace, tcTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) { + var referredQuery = constraint.getSupplierKey(); + var callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + var recipe = FACTORY.createRepresentativeElectionRecipe(); + recipe.setParent(callTrace.getRecipe()); + recipe.setConnectivity(constraint.getConnectivity()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) { + final PQuery referredQuery = constraint.getSupplierKey(); + final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + + final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe(); + recipe.setParent(callTrace.getRecipe()); + + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) { + final List parentRecipes = new ArrayList(); + final List parentTraceInfos = new ArrayList(); + for (final PQuery inputQuery : constraint.getReferredQueries()) { + final CompiledQuery compiledQuery = getCompiledForm(inputQuery); + parentRecipes.add(compiledQuery.getRecipe()); + parentTraceInfos.add(compiledQuery); + } + final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe(); + recipe.getParents().addAll(parentRecipes); + recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator())); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos); + } + + private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) { + final PQuery referredQuery = constraint.getReferredQuery(); + return referQuery(referredQuery, plan, constraint.getVariablesTuple()); + } + + private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) { + final IInputKey inputKey = constraint.getSupplierKey(); + final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); + } + + private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) { + final ConstantRecipe recipe = FACTORY.createConstantRecipe(); + recipe.getConstantValues().add(constraint.getSupplierKey()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); + } + + // TODO handle recursion + private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) { + RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple), + referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning()); + } + + private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) { + // eliminate superfluous production node? + if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only + Set rewrittenBodies = normalizer.rewrite(query).getBodies(); + if (1 == rewrittenBodies.size()) { // non-disjunctive + // TODO in the future, check if non-recursive - (not currently permitted) + + PBody pBody = rewrittenBodies.iterator().next(); + SubPlan bodyFinalPlan = getPlan(pBody); + + // skip over any projections at the end + bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); + + // TODO checkAndTrimEqualVariables may introduce superfluous trim, + // but whatever (no uniqueness enforcer needed) + + // compile body + final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); + + // project to parameter list, add uniqueness enforcer if necessary + return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is used */); + } + } + + // otherwise, regular reference to recipe realizing the query + return getCompiledForm(query); + } + + protected List getCompiledFormOfParents(SubPlan plan) { + List results = new ArrayList(); + for (SubPlan parentPlan : plan.getParentPlans()) { + results.add(getCompiledForm(parentPlan)); + } + return results; + } + + /** + * Returns an unmodifiable view of currently cached compiled queries. + */ + public Map getCachedCompiledQueries() { + return Collections.unmodifiableMap(queryCompilerCache); + } + + /** + * Returns an unmodifiable view of currently cached query plans. + */ + public Map getCachedQueryPlans() { + return Collections.unmodifiableMap(plannerCache); + } + + private QueryEvaluationHint getHints(SubPlan plan) { + return getHints(plan.getBody().getPattern()); + } + + private QueryEvaluationHint getHints(PQuery pattern) { + return hintProvider.getQueryEvaluationHint(pattern); + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PJoin; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Gabor Bergmann + * + */ +class JoinCandidate { + private QueryAnalyzer analyzer; + + SubPlan primary; + SubPlan secondary; + + Set varPrimary; + Set varSecondary; + Set varCommon; + + List consPrimary; + List consSecondary; + + + JoinCandidate(SubPlan primary, SubPlan secondary, QueryAnalyzer analyzer) { + super(); + this.primary = primary; + this.secondary = secondary; + this.analyzer = analyzer; + + varPrimary = getPrimary().getVisibleVariables(); + varSecondary = getSecondary().getVisibleVariables(); + varCommon = CollectionsFactory.createSet(varPrimary); + varCommon.retainAll(varSecondary); + + consPrimary = new ArrayList(primary.getAllEnforcedConstraints()); + Collections.sort(consPrimary, TieBreaker.CONSTRAINT_COMPARATOR); + consSecondary = new ArrayList(secondary.getAllEnforcedConstraints()); + Collections.sort(consSecondary, TieBreaker.CONSTRAINT_COMPARATOR); + } + + + + /** + * @return the a + */ + public SubPlan getPrimary() { + return primary; + } + + /** + * @return the b + */ + public SubPlan getSecondary() { + return secondary; + } + + public SubPlan getJoinedPlan(SubPlanFactory factory) { + // check special cases first + if (isTrivial()) + return primary; + if (isSubsumption()) + return + (consPrimary.size() > consSecondary.size()) ? primary : secondary; + + + // default case + return factory.createSubPlan(new PJoin(), primary, secondary); + } + + @Override + public String toString() { + return primary.toString() + " |x| " + secondary.toString(); + } + + /** + * @return the varPrimary + */ + public Set getVarPrimary() { + return varPrimary; + } + + /** + * @return the varSecondary + */ + public Set getVarSecondary() { + return varSecondary; + } + + /** + * @return constraints of primary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}. + */ + public List getConsPrimary() { + return consPrimary; + } + /** + * @return constraints of secondary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}. + */ + public List getConsSecondary() { + return consSecondary; + } + + + + public boolean isTrivial() { + return getPrimary().equals(getSecondary()); + } + + public boolean isSubsumption() { + return consPrimary.containsAll(consSecondary) || consSecondary.containsAll(consPrimary); + } + + public boolean isCheckOnly() { + return varPrimary.containsAll(varSecondary) || varSecondary.containsAll(varPrimary); + } + + public boolean isDescartes() { + return Collections.disjoint(varPrimary, varSecondary); + } + + private Boolean heath; + + // it is a Heath-join iff common variables functionally determine either all primary or all secondary variables + public boolean isHeath() { + if (heath == null) { + Set union = Stream.concat( + primary.getAllEnforcedConstraints().stream(), + secondary.getAllEnforcedConstraints().stream() + ).collect(Collectors.toSet()); + Map, Set> dependencies = + analyzer.getFunctionalDependencies(union, false); + // does varCommon determine either varPrimary or varSecondary? + Set varCommonClosure = FunctionalDependencyHelper.closureOf(varCommon, dependencies); + + heath = varCommonClosure.containsAll(varPrimary) || varCommonClosure.containsAll(varSecondary); + } + return heath; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.viatra.runtime.rete.util.Options; +import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent; + +/** + * @author Gabor Bergmann + * + */ +public class JoinOrderingHeuristics implements Comparator { + + @Override + public int compare(JoinCandidate jc1, JoinCandidate jc2) { + return new OrderingCompareAgent(jc1, jc2) { + @Override + protected void doCompare() { + swallowBoolean(true && consider(preferTrue(a.isTrivial(), b.isTrivial())) + && consider(preferTrue(a.isSubsumption(), b.isSubsumption())) + && consider(preferTrue(a.isCheckOnly(), b.isCheckOnly())) + && consider( + Options.functionalDependencyOption == Options.FunctionalDependencyOption.OFF ? + dontCare() : + preferTrue(a.isHeath(), b.isHeath()) + ) + && consider(preferFalse(a.isDescartes(), b.isDescartes())) + + // TODO main heuristic decisions + + // tie breaking + && consider(preferLess(a.getConsPrimary(), b.getConsPrimary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR)) + && consider(preferLess(a.getConsSecondary(), b.getConsSecondary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR)) + && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b)))); + } + }.compare(); + + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.PEnumerate; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +/** + * Layout ideas: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=398763 + * + * @author Gabor Bergmann + * + */ +public class QuasiTreeLayout implements IQueryPlannerStrategy { + + private IQueryBackendHintProvider hintProvider; + private IQueryBackendContext backendContext; + private QueryAnalyzer queryAnalyzer; + + public QuasiTreeLayout(IQueryBackendContext backendContext) { + this(backendContext, backendContext.getHintProvider()); + } + + public QuasiTreeLayout(IQueryBackendContext backendContext, IQueryBackendHintProvider hintProvider) { + this.backendContext = backendContext; + this.hintProvider = hintProvider; + queryAnalyzer = backendContext.getQueryAnalyzer(); + } + + @Override + public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context) { + return new Scaffold(pSystem, logger, context).run(); + } + + public class Scaffold { + PBody pSystem; + PQuery query; + IQueryMetaContext context; + private QueryEvaluationHint hints; + //IOperationCompiler compiler; + //SubPlanProcessor planProcessor = new SubPlanProcessor(); + SubPlanFactory planFactory; + + Set deferredConstraints = null; + Set enumerableConstraints = null; + Set constantConstraints = null; + Set forefront = new LinkedHashSet(); + Logger logger; + + Scaffold(PBody pSystem, Logger logger, /*IOperationCompiler compiler,*/ IQueryMetaContext context) { + this.pSystem = pSystem; + this.logger = logger; + this.context = context; + this.planFactory = new SubPlanFactory(pSystem); + query = pSystem.getPattern(); + //this.compiler = compiler; + //planProcessor.setCompiler(compiler); + + hints = hintProvider.getQueryEvaluationHint(query); + } + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan run() { + try { + logger.debug(String.format( + "%s: patternbody build started for %s", + getClass().getSimpleName(), + query.getFullyQualifiedName())); + + // PROCESS CONSTRAINTS + deferredConstraints = pSystem.getConstraintsOfType(DeferredPConstraint.class); + enumerableConstraints = pSystem.getConstraintsOfType(EnumerablePConstraint.class); + constantConstraints = pSystem.getConstraintsOfType(ConstantValue.class); + + for (EnumerablePConstraint enumerable : enumerableConstraints) { + SubPlan plan = planFactory.createSubPlan(new PEnumerate(enumerable)); + admitSubPlan(plan); + } + if (enumerableConstraints.isEmpty()) { // EXTREME CASE + SubPlan plan = planFactory.createSubPlan(new PStart()); + admitSubPlan(plan); + } + + // JOIN FOREFRONT PLANS WHILE POSSIBLE + while (forefront.size() > 1) { + // TODO QUASI-TREE TRIVIAL JOINS? + + List candidates = generateJoinCandidates(); + JoinOrderingHeuristics ordering = new JoinOrderingHeuristics(); + JoinCandidate selectedJoin = Collections.min(candidates, ordering); + doJoin(selectedJoin); + } + assert (forefront.size() == 1); + + // PROJECT TO PARAMETERS + SubPlan preFinalPlan = forefront.iterator().next(); + SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), preFinalPlan); + + // FINAL CHECK, whether all exported variables are present + all constraint satisfied + BuildHelper.finalCheck(pSystem, finalPlan, context); + // TODO integrate the check above in SubPlan / POperation + + logger.debug(String.format( + "%s: patternbody query plan concluded for %s as: %s", + getClass().getSimpleName(), + query.getFullyQualifiedName(), + finalPlan.toLongString())); + return finalPlan; + } catch (RetePatternBuildException ex) { + ex.setPatternDescription(query); + throw ex; + } + } + + public List generateJoinCandidates() { + List candidates = new ArrayList(); + int bIndex = 0; + for (SubPlan b : forefront) { + int aIndex = 0; + for (SubPlan a : forefront) { + if (aIndex++ >= bIndex) + break; + candidates.add(new JoinCandidate(a, b, queryAnalyzer)); + } + bIndex++; + } + return candidates; + } + + private void admitSubPlan(SubPlan plan) { + // are there any unapplied constant filters that we can apply here? + if (ReteHintOptions.prioritizeConstantFiltering.getValueOrDefault(hints)) { + for (ConstantValue constantConstraint : constantConstraints) { + if (!plan.getAllEnforcedConstraints().contains(constantConstraint) && + plan.getVisibleVariables().containsAll(constantConstraint.getAffectedVariables())) { + plan = planFactory.createSubPlan(new PApply(constantConstraint), plan); + } + } + } + // are there any variables that will not be needed anymore and are worth trimming? + // (check only if there are unenforced enumerables, so that there are still upcoming joins) +// if (Options.planTrimOption != Options.PlanTrimOption.OFF && +// !plan.getAllEnforcedConstraints().containsAll(enumerableConstraints)) { + if (true) { + final SubPlan trimmed = BuildHelper.trimUnneccessaryVariables( + planFactory, plan, true, queryAnalyzer); + plan = trimmed; + } + // are there any checkable constraints? + for (DeferredPConstraint deferred : deferredConstraints) { + if (!plan.getAllEnforcedConstraints().contains(deferred)) { + if (deferred.isReadyAt(plan, context)) { + admitSubPlan(planFactory.createSubPlan(new PApply(deferred), plan)); + return; + } + } + } + // if no checkable constraints and no unused variables + forefront.add(plan); + } + + private void doJoin(JoinCandidate selectedJoin) { + forefront.remove(selectedJoin.getPrimary()); + forefront.remove(selectedJoin.getSecondary()); + admitSubPlan(selectedJoin.getJoinedPlan(planFactory)); + } + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.rete.util.LexicographicComparator; + +/** + * Class providing comparators for breaking ties somewhat more deterministically. + * @author Bergmann Gabor + * + */ +public class TieBreaker { + + private TieBreaker() {/*Utility class constructor*/} + + public static final Comparator CONSTRAINT_COMPARATOR = (arg0, arg1) -> arg0.getMonotonousID() - arg1.getMonotonousID(); + + public static final Comparator> CONSTRAINT_LIST_COMPARATOR = + new LexicographicComparator(CONSTRAINT_COMPARATOR); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Iterator; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * @author Bergmann Gabor + */ +public abstract class AbstractEvaluatorNode extends SingleInputNode implements IEvaluatorNode { + + /** + * @since 1.5 + */ + protected EvaluatorCore core; + + + /** + * @since 1.5 + */ + public AbstractEvaluatorNode(ReteContainer reteContainer, EvaluatorCore core) { + super(reteContainer); + this.core = core; + core.init(this); + } + + /** + * @since 1.5 + */ + @Override + public ReteContainer getReteContainer() { + return getContainer(); + } + + /** + * @since 1.5 + */ + @Override + public String prettyPrintTraceInfoPatternList() { + return getTraceInfoPatternsEnumerated(); + } + + /** + * @since 2.4 + */ + protected void propagateIterableUpdate(final Direction direction, final Iterable update, final Timestamp timestamp) { + final Iterator itr = update.iterator(); + while (itr.hasNext()) { + propagateUpdate(direction, itr.next(), timestamp); + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleValueProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Sets; + +/** + * An instance of this class performs the evaluation of Java expressions. + * + * @author Bergmann Gabor + * @author Tamas Szabo + * @since 1.5 + */ +public abstract class EvaluatorCore { + + protected Logger logger; + protected IExpressionEvaluator evaluator; + /** + * @since 2.4 + */ + protected int sourceTupleWidth; + private Map parameterPositions; + protected IQueryRuntimeContext runtimeContext; + protected IEvaluatorNode evaluatorNode; + + public EvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + this.logger = logger; + this.evaluator = evaluator; + this.parameterPositions = parameterPositions; + this.sourceTupleWidth = sourceTupleWidth; + } + + public void init(final IEvaluatorNode evaluatorNode) { + this.evaluatorNode = evaluatorNode; + this.runtimeContext = evaluatorNode.getReteContainer().getNetwork().getEngine().getRuntimeContext(); + } + + /** + * @since 2.4 + */ + public abstract Iterable performEvaluation(final Tuple input); + + protected abstract String evaluationKind(); + + public Object evaluateTerm(final Tuple input) { + // actual evaluation + Object result = null; + try { + final TupleValueProvider tupleParameters = new TupleValueProvider(runtimeContext.unwrapTuple(input), + parameterPositions); + result = evaluator.evaluateExpression(tupleParameters); + } catch (final Exception e) { + logger.warn(String.format( + "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)", + evaluationKind(), evaluatorNode.prettyPrintTraceInfoPatternList(), prettyPrintTuple(input), + e.getMessage(), e.getClass().getSimpleName(), this.evaluatorNode), e); + result = errorResult(); + } + + return result; + } + + protected String prettyPrintTuple(final Tuple tuple) { + return tuple.toString(); + } + + protected Object errorResult() { + return null; + } + + public static class PredicateEvaluatorCore extends EvaluatorCore { + + public PredicateEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + super(logger, evaluator, parameterPositions, sourceTupleWidth); + } + + @Override + public Iterable performEvaluation(final Tuple input) { + final Object result = evaluateTerm(input); + if (Boolean.TRUE.equals(result)) { + return Collections.singleton(input); + } else { + return null; + } + } + + @Override + protected String evaluationKind() { + return "check()"; + } + + } + + public static class FunctionEvaluatorCore extends EvaluatorCore { + + /** + * @since 2.4 + */ + protected final boolean isUnwinding; + + public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + this(logger, evaluator, parameterPositions, sourceTupleWidth, false); + } + + /** + * @since 2.4 + */ + public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth, final boolean isUnwinding) { + super(logger, evaluator, parameterPositions, sourceTupleWidth); + this.isUnwinding = isUnwinding; + } + + @Override + public Iterable performEvaluation(final Tuple input) { + final Object result = evaluateTerm(input); + if (result != null) { + if (this.isUnwinding) { + final Set resultAsSet = (result instanceof Set) ? (Set) result + : (result instanceof Iterable) ? Sets.newSet((Iterable) result) : null; + + if (resultAsSet != null) { + return () -> { + final Iterator wrapped = resultAsSet.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public Tuple next() { + final Object next = wrapped.next(); + return Tuples.staticArityLeftInheritanceTupleOf(input, + runtimeContext.wrapElement(next)); + } + }; + }; + } else { + throw new IllegalStateException( + "This is an unwinding evaluator, which expects the evaluation result to either be a set or an iterable, but it was " + + result); + } + } else { + return Collections.singleton( + Tuples.staticArityLeftInheritanceTupleOf(input, runtimeContext.wrapElement(result))); + } + } else { + return null; + } + } + + @Override + protected String evaluationKind() { + return "eval" + (this.isUnwinding ? "Unwind" : "") + "()"; + } + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * This interface is required for the communication between the evaluation core end the evaluator node. + * @author Gabor Bergmann + * @since 1.5 + */ +public interface IEvaluatorNode { + + ReteContainer getReteContainer(); + + String prettyPrintTraceInfoPatternList(); + + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Bergmann Gabor + * + */ +public class MemorylessEvaluatorNode extends AbstractEvaluatorNode { + + /** + * @since 1.5 + */ + public MemorylessEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) { + super(reteContainer, core); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + final Collection parentTuples = new ArrayList(); + propagatePullInto(parentTuples, flush); + for (final Tuple parentTuple : parentTuples) { + final Iterable output = core.performEvaluation(parentTuple); + if (output != null) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.add(itr.next()); + } + } + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Map> parentTuples = CollectionsFactory.createMap(); + propagatePullIntoWithTimestamp(parentTuples, flush); + for (final Entry> entry : parentTuples.entrySet()) { + final Iterable output = core.performEvaluation(entry.getKey()); + if (output != null) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.put(itr.next(), entry.getValue()); + } + } + } + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + final Iterable output = core.performEvaluation(input); + if (output != null) { + propagateIterableUpdate(direction, output, timestamp); + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * An evaluator node that caches the evaluation result. This node is also capable of caching the timestamps associated + * with the result tuples if it is used in recursive differential dataflow evaluation. + * + * @author Bergmann Gabor + * @author Tamas Szabo + */ +public class OutputCachingEvaluatorNode extends AbstractEvaluatorNode implements Clearable, ResumableNode { + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + /** + * @since 2.4 + */ + protected Map> outputCache; + + /** + * Maps input tuples to timestamps. It is wrong to map evaluation result to timestamps because the different input + * tuples may yield the same evaluation result. This field is null as long as this node is in a non-recursive group. + * + * @since 2.4 + */ + protected TimelyMemory memory; + + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + /** + * @since 1.5 + */ + public OutputCachingEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) { + super(reteContainer, core); + reteContainer.registerClearable(this); + this.outputCache = CollectionsFactory.createMap(); + this.logic = createLogic(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public void clear() { + this.outputCache.clear(); + if (this.memory != null) { + this.memory.clear(); + } + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + if (this.memory == null) { + this.memory = new TimelyMemory(reteContainer.isTimelyEvaluation() && reteContainer + .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + } + return TIMELY; + } else { + return TIMELESS; + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + this.logic.pullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + this.logic.pullIntoWithTimeline(collector, flush); + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + this.logic.update(direction, input, timestamp); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + if (this.memory == null) { + return null; + } else { + return this.memory.getResumableTimestamp(); + } + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + this.logic.resumeAt(timestamp); + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * @since 2.4 + */ + public abstract void update(final Direction direction, final Tuple input, final Timestamp timestamp); + + public abstract void pullInto(final Collection collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void pullIntoWithTimeline(final Map> collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void resumeAt(final Timestamp timestamp); + + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + // there is nothing to resume in the timeless case because we do not even care about timestamps + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Iterable output : outputCache.values()) { + if (output != NORESULT) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.add(itr.next()); + } + } + } + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + if (direction == Direction.INSERT) { + final Iterable output = core.performEvaluation(input); + if (output != null) { + final Iterable previous = outputCache.put(input, output); + if (previous != null) { + throw new IllegalStateException( + String.format("Duplicate insertion of tuple %s into node %s", input, this)); + } + propagateIterableUpdate(direction, output, timestamp); + } + } else { + final Iterable output = outputCache.remove(input); + if (output != null) { + // may be null if no result was yielded + propagateIterableUpdate(direction, output, timestamp); + } + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + final Map> diffMap = memory.resumeAt(timestamp); + + for (final Entry> entry : diffMap.entrySet()) { + final Tuple input = entry.getKey(); + final Iterable output = outputCache.get(input); + if (output != NORESULT) { + for (final Signed signed : entry.getValue()) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + + if (memory.get(input) == null) { + outputCache.remove(input); + } + } + + final Timestamp nextTimestamp = memory.getResumableTimestamp(); + if (nextTimestamp != null) { + group.notifyHasMessage(mailbox, nextTimestamp); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + for (final Entry> entry : memory.asMap().entrySet()) { + final Tuple input = entry.getKey(); + final Iterable output = outputCache.get(input); + if (output != NORESULT) { + final Timeline timestamp = entry.getValue(); + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.put(itr.next(), timestamp); + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + TIMELESS.pullInto(collector, flush); + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + if (direction == Direction.INSERT) { + Iterable output = outputCache.get(input); + if (output == null) { + output = core.performEvaluation(input); + if (output == null) { + // the evaluation result is really null + output = NORESULT; + } + outputCache.put(input, output); + } + final Diff diff = memory.put(input, timestamp); + if (output != NORESULT) { + for (final Signed signed : diff) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + } else { + final Iterable output = outputCache.get(input); + final Diff diff = memory.remove(input, timestamp); + if (memory.get(input) == null) { + outputCache.remove(input); + } + if (output != NORESULT) { + for (final Signed signed : diff) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + } + } + }; + + /** + * This field is used to represent the "null" evaluation result. This is an optimization used in the timely case + * where the same tuple may be inserted multiple times with different timestamps. This way, we can also cache if + * something evaluated to null (instead of just forgetting about the previously computed result), thus avoiding the + * need to re-run a potentially expensive evaluation. + */ + private static final Iterable NORESULT = Collections + .singleton(Tuples.staticArityFlatTupleOf(NoResult.INSTANCE)); + + private enum NoResult { + INSTANCE + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2022, Tamas Szabo, GitHub + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.RelationEvaluation; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.AbstractUniquenessEnforcerNode; + +/** + * A node that operates in batch-style (see {@link Receiver#doesProcessUpdatesInBatch()} and evaluates arbitrary Java + * logic represented by an {@link IRelationEvaluator} on the input relations. This is the backing computation node of a + * {@link RelationEvaluation} constraint. + * + * @author Tamas Szabo + * @since 2.8 + */ +public class RelationEvaluatorNode extends StandardNode implements Supplier, Clearable { + + private final IRelationEvaluator evaluator; + private Set cachedOutputs; + private Supplier[] inputSuppliers; + private BatchingReceiver[] inputReceivers; + + public RelationEvaluatorNode(final ReteContainer container, final IRelationEvaluator evaluator) { + super(container); + this.evaluator = evaluator; + this.reteContainer.registerClearable(this); + } + + @Override + public void clear() { + this.cachedOutputs.clear(); + } + + public void connectToParents(final List inputSuppliers) { + this.inputSuppliers = new Supplier[inputSuppliers.size()]; + this.inputReceivers = new BatchingReceiver[inputSuppliers.size()]; + + final List inputArities = evaluator.getInputArities(); + + if (inputArities.size() != inputSuppliers.size()) { + throw new IllegalStateException(evaluator.toString() + " expects " + inputArities.size() + + " inputs, but got " + inputSuppliers.size() + " input(s)!"); + } + + for (int i = 0; i < inputSuppliers.size(); i++) { + final int currentExpectedInputArity = inputArities.get(i); + final Supplier inputSupplier = inputSuppliers.get(i); + // it is expected that the supplier is a production node because + // the corresponding constraint itself accepts a list of PQuery + if (!(inputSupplier instanceof ProductionNode)) { + throw new IllegalStateException( + evaluator.toString() + " expects each one of its suppliers to be instances of " + + ProductionNode.class.getSimpleName() + " but got an instance of " + + inputSupplier.getClass().getSimpleName() + "!"); + } + final int currentActualInputArity = ((ProductionNode) inputSupplier).getPosMapping().size(); + if (currentActualInputArity != currentExpectedInputArity) { + throw new IllegalStateException( + evaluator.toString() + " expects input arity " + currentExpectedInputArity + " at position " + i + + " but got " + currentActualInputArity + "!"); + } + final BatchingReceiver inputReceiver = new BatchingReceiver((ProductionNode) inputSupplier, + this.reteContainer); + this.inputSuppliers[i] = inputSupplier; + this.inputReceivers[i] = inputReceiver; + this.reteContainer.connectAndSynchronize(inputSupplier, inputReceiver); + reteContainer.getCommunicationTracker().registerDependency(inputReceiver, this); + } + + // initialize the output relation + final List> inputSets = new ArrayList>(); + for (final BatchingReceiver inputReceiver : this.inputReceivers) { + inputSets.add(inputReceiver.getTuples()); + } + this.cachedOutputs = evaluateRelation(inputSets); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive evaluation!"); + } + super.networkStructureChanged(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + collector.addAll(this.cachedOutputs); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Timeline timeline = Timelines.createFrom(Timestamp.ZERO); + for (final Tuple output : this.cachedOutputs) { + collector.put(output, timeline); + } + } + + private Set evaluateRelation(final List> inputs) { + try { + return this.evaluator.evaluateRelation(inputs); + } catch (final Exception e) { + throw new IllegalStateException("Exception during the evaluation of " + this.evaluator.toString() + "!", e); + } + } + + private void batchUpdateCompleted() { + final List> inputSets = new ArrayList>(); + for (final BatchingReceiver inputReceiver : this.inputReceivers) { + inputSets.add(inputReceiver.getTuples()); + } + final Set newOutputs = evaluateRelation(inputSets); + for (final Tuple tuple : newOutputs) { + if (this.cachedOutputs != null && this.cachedOutputs.remove(tuple)) { + // already known tuple - do nothing + } else { + // newly inserted tuple + propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO); + } + } + if (this.cachedOutputs != null) { + for (final Tuple tuple : this.cachedOutputs) { + // lost tuple + propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO); + } + } + this.cachedOutputs = newOutputs; + } + + public class BatchingReceiver extends SimpleReceiver { + private final ProductionNode source; + + private BatchingReceiver(final ProductionNode source, final ReteContainer container) { + super(container); + this.source = source; + } + + private Set getTuples() { + return ((AbstractUniquenessEnforcerNode) this.source).getTuples(); + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + throw new UnsupportedOperationException("This receiver only supports batch-style operation!"); + } + + @Override + public void batchUpdate(final Collection> updates, final Timestamp timestamp) { + assert Timestamp.ZERO.equals(timestamp); + // there is nothing to do here because the source production node has already updated itself + // the only thing we need to do is to issue the callback + RelationEvaluatorNode.this.batchUpdateCompleted(); + } + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index; + +import java.lang.ref.WeakReference; + +import tools.refinery.viatra.runtime.rete.network.Node; + +public abstract class DefaultIndexerListener implements IndexerListener { + + WeakReference owner; + + public DefaultIndexerListener(Node owner) { + this.owner = new WeakReference(owner); + } + + @Override + public Node getOwner() { + return owner.get(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp.AllZeroMap; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * Abstract superclass for nodes with two inputs that are matched against each other. + * + * @author Gabor Bergmann + */ +public abstract class DualInputNode extends StandardNode implements NetworkStructureChangeSensitiveNode { + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public IterableIndexer getPrimarySlot() { + return primarySlot; + } + + public Indexer getSecondarySlot() { + return secondarySlot; + } + + /** + * @author Gabor Bergmann + * + */ + public enum Side { + PRIMARY, SECONDARY, BOTH; + + public Side opposite() { + switch (this) { + case PRIMARY: + return SECONDARY; + case SECONDARY: + return PRIMARY; + case BOTH: + return BOTH; + default: + return BOTH; + } + } + } + + /** + * Holds the primary input slot of this node. + */ + protected IterableIndexer primarySlot; + + /** + * Holds the secondary input slot of this node. + */ + protected Indexer secondarySlot; + + /** + * Optional complementer mask + */ + protected TupleMask complementerSecondaryMask; + + /** + * true if the primary and secondary slots coincide + */ + protected boolean coincidence; + + /** + * @param reteContainer + */ + public DualInputNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) { + super(reteContainer); + this.complementerSecondaryMask = complementerSecondaryMask; + this.indexerGroupCache = CollectionsFactory.createMap(); + this.refreshIndexerGroupCache(); + } + + /** + * Should be called only once, when node is initialized. + */ + public void connectToIndexers(final IterableIndexer primarySlot, final Indexer secondarySlot) { + this.primarySlot = primarySlot; + this.secondarySlot = secondarySlot; + + reteContainer.getCommunicationTracker().registerDependency(primarySlot, this); + reteContainer.getCommunicationTracker().registerDependency(secondarySlot, this); + + // attach listeners + // if there is syncing, do this after the flush done for pulling, but before syncing updates + coincidence = primarySlot.equals(secondarySlot); + + if (!coincidence) { // regular case + primarySlot.attachListener(new DefaultIndexerListener(this) { + @Override + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.PRIMARY, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "primary@" + DualInputNode.this; + } + }); + secondarySlot.attachListener(new DefaultIndexerListener(this) { + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.SECONDARY, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "secondary@" + DualInputNode.this; + } + }); + } else { // if the two slots are the same, updates have to be handled carefully + primarySlot.attachListener(new DefaultIndexerListener(this) { + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.BOTH, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "both@" + DualInputNode.this; + } + }); + } + + for (final Receiver receiver : getReceivers()) { + this.reteContainer.getDelayedCommandQueue() + .add(new DelayedConnectCommand(this, receiver, this.reteContainer)); + } + + // Given that connectToIndexers registers new dependencies, the networkStructureChanged() method will be called + // by the CommunicationTracker, and the implementation of that method in turn will call refreshIndexerGroupCache() anyway. + this.refreshIndexerGroupCache(); + } + + /** + * Helper: retrieves all stored substitutions from the opposite side memory. + * + * @return the collection of opposite substitutions if any, or null if none + */ + protected Collection retrieveOpposites(final Side side, final Tuple signature) { + return getSlot(side.opposite()).get(signature); + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return createTimelyLogic(); + } else { + return createTimelessLogic(); + } + } + + /** + * Helper: unifies a left and right partial matching. + */ + protected Tuple unify(final Tuple left, final Tuple right) { + return complementerSecondaryMask.combine(left, right, Options.enableInheritance, true); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + this.logic.pullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + this.logic.pullIntoWithTimeline(collector, flush); + } + + /** + * Helper: unifies a substitution from the specified side with another substitution from the other side. + */ + protected Tuple unify(final Side side, final Tuple ps, final Tuple opposite) { + switch (side) { + case PRIMARY: + return unify(ps, opposite); + case SECONDARY: + return unify(opposite, ps); + case BOTH: + return unify(ps, opposite); + default: + return null; + } + } + + /** + * Simulates the behavior of the node for calibration purposes only. + */ + public abstract Tuple calibrate(final Tuple primary, final Tuple secondary); + + /** + * @param complementerSecondaryMask + * the complementerSecondaryMask to set + */ + public void setComplementerSecondaryMask(final TupleMask complementerSecondaryMask) { + this.complementerSecondaryMask = complementerSecondaryMask; + } + + /** + * Retrieves the slot corresponding to the specified side. + */ + protected Indexer getSlot(final Side side) { + if (side == Side.SECONDARY) { + return secondarySlot; + } else { + return primarySlot; + } + } + + @Override + public void assignTraceInfo(final TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateToIndexerParent()) { + if (primarySlot != null) { + primarySlot.acceptPropagatedTraceInfo(traceInfo); + } + if (secondarySlot != null) { + secondarySlot.acceptPropagatedTraceInfo(traceInfo); + } + } + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + this.refreshIndexerGroupCache(); + } + + /** + * @since 2.3 + */ + protected abstract NetworkStructureChangeSensitiveLogic createTimelyLogic(); + + /** + * @since 2.3 + */ + protected abstract NetworkStructureChangeSensitiveLogic createTimelessLogic(); + + /** + * This map caches the result of a CommunicationTracker.areInSameGroup(indexer, this) call. It does that for both + * the primary and secondary slots. This way we can avoid the lookup in the getWithTimestamp call for each tuple. + * The cache needs to be maintained when the network structure changes. + * @since 2.3 + */ + protected Map indexerGroupCache; + + /** + * @since 2.3 + */ + protected void refreshIndexerGroupCache() { + this.indexerGroupCache.clear(); + if (this.primarySlot != null) { + this.indexerGroupCache.put(this.primarySlot, + this.reteContainer.getCommunicationTracker().areInSameGroup(this.primarySlot, this)); + } + if (this.secondarySlot != null) { + this.indexerGroupCache.put(this.secondarySlot, + this.reteContainer.getCommunicationTracker().areInSameGroup(this.secondarySlot, this)); + } + } + + /** + * @since 2.4 + */ + protected Map> getTimeline(final Tuple signature, final Indexer indexer) { + if (this.indexerGroupCache.get(indexer)) { + // recursive timely case + return indexer.getTimeline(signature); + } else { + // the indexer is in a different group, treat all of its tuples as they would have timestamp 0 + final Collection tuples = indexer.get(signature); + if (tuples == null) { + return null; + } else { + return new AllZeroMap((Set) tuples); + } + } + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * Abstract handler for update event. + * + * @param side + * The side on which the event occurred. + * @param direction + * The direction of the update. + * @param updateElement + * The partial matching that is inserted. + * @param signature + * Masked signature of updateElement. + * @param change + * Indicates whether this is/was the first/last instance of this signature in this slot. + * @since 2.4 + */ + public abstract void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp); + + public abstract void pullInto(final Collection collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void pullIntoWithTimeline(final Map> collector, final boolean flush); + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Propagates all substitutions arriving at the PRIMARY slot if and only if (a matching substitution on the SECONDARY is + * present) xor (NEGATIVE). + * + * The negative parameter specifies whether this node checks for existence or non-existence. + *

+ * It is mandatory in differential dataflow evaluation that the secondary parent is in an upstream dependency component + * (so that every secondary tuple comes with zero timestamp). + * + * @author Gabor Bergmann + */ +public class ExistenceNode extends DualInputNode { + + protected boolean negative; + + /** + * @param reteContainer + * @param negative + * if false, act as existence checker, otherwise a nonexistence-checker + */ + public ExistenceNode(final ReteContainer reteContainer, final boolean negative) { + super(reteContainer, null); + this.negative = negative; + this.logic = createLogic(); + } + + @Override + public Tuple calibrate(final Tuple primary, final Tuple secondary) { + return primary; + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.secondarySlot != null + && this.reteContainer.getCommunicationTracker().areInSameGroup(this, this.secondarySlot)) { + throw new IllegalStateException("Secondary parent must be in an upstream dependency component!"); + } + super.networkStructureChanged(); + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Collection primaries = primarySlot.get(signature); + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + collector.addAll(primaries); + } + } + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + // in the default case, all timestamps must be zero + assert Timestamp.ZERO.equals(timestamp); + + switch (side) { + case PRIMARY: + if ((retrieveOpposites(side, signature) != null) ^ negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + case SECONDARY: + if (change) { + final Collection opposites = retrieveOpposites(side, signature); + if (opposites != null) { + for (final Tuple opposite : opposites) { + propagateUpdate((negative ? direction.opposite() : direction), opposite, timestamp); + } + } + } + break; + case BOTH: + // in case the slots coincide, + // negative --> always empty + // !positive --> identity + if (!negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Map> primaries = getTimeline(signature, primarySlot); + // see contract: secondary must be in an upstream SCC + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + for (final Tuple primary : primaries.keySet()) { + collector.put(primary, primaries.get(primary)); + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + ExistenceNode.this.TIMELESS.pullInto(collector, flush); + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + switch (side) { + case PRIMARY: { + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + case SECONDARY: { + final Map> opposites = primarySlot.getTimeline(signature); + if (change) { + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + for (final Signed oppositeSigned : opposites.get(opposite).asChangeSequence()) { + final Direction product = direction.multiply(oppositeSigned.getDirection()); + propagateUpdate((negative ? product.opposite() : product), opposite, + oppositeSigned.getPayload()); + } + } + } + } + break; + } + case BOTH: + // in case the slots coincide, + // negative --> always empty + // positive --> identity + if (!negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + } + }; + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelessLogic() { + return this.TIMELESS; + } + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelyLogic() { + return this.TIMELY; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A generic Indexer capable of indexing along any valid TupleMask. Does not keep track of parents, because will not + * ever pull parents. + * + * @author Gabor Bergmann + * + */ +public class GenericProjectionIndexer extends IndexerWithMemory implements ProjectionIndexer { + + public GenericProjectionIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer, mask); + } + + @Override + protected void update(Direction direction, Tuple updateElement, Tuple signature, boolean change, + Timestamp timestamp) { + propagate(direction, updateElement, signature, change, timestamp); + } + + @Override + public Collection get(Tuple signature) { + return memory.get(signature); + } + + @Override + public Map> getTimeline(Tuple signature) { + return memory.getWithTimeline(signature); + } + + @Override + public Iterator iterator() { + return memory.iterator(); + } + + @Override + public Iterable getSignatures() { + return memory.getSignatures(); + } + + /** + * @since 2.0 + */ + @Override + public int getBucketCount() { + return memory.getKeysetSize(); + } + + @Override + public Receiver getActiveNode() { + return this; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Defines an abstract trivial indexer that identically projects the contents of some stateful node, and can therefore + * save space. Can only exist in connection with a stateful store, and must be operated by another node (the active + * node). Do not attach parents directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class IdentityIndexer extends SpecializedProjectionIndexer { + + protected abstract Collection getTuples(); + + public IdentityIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent, + Node activeNode, List sharedSubscriptionList) { + super(reteContainer, TupleMask.identity(tupleWidth), parent, activeNode, sharedSubscriptionList); + } + + @Override + public Collection get(Tuple signature) { + if (contains(signature)) { + return Collections.singleton(signature); + } else + return null; + } + + protected boolean contains(Tuple signature) { + return getTuples().contains(signature); + } + + @Override + public Collection getSignatures() { + return getTuples(); + } + + @Override + public int getBucketCount() { + return getTuples().size(); + } + + @Override + public Iterator iterator() { + return getTuples().iterator(); + } + + @Override + public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, Timestamp timestamp) { + listener.notifyIndexerUpdate(direction, updateElement, updateElement, true, timestamp); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A node that indexes incoming Tuples by their signatures as specified by a TupleMask. Notifies listeners about such + * update events through the IndexerListener. + * + * Signature tuples are created by transforming the update tuples using the mask. Tuples stored with the same signature + * are grouped together. The group or a reduction thereof is retrievable. + * + * @author Gabor Bergmann + */ +public interface Indexer extends Node { + /** + * @return the mask by which the contents are indexed. + */ + public TupleMask getMask(); + + /** + * @return the node whose contents are indexed. + */ + public Supplier getParent(); + + /** + * @return all stored tuples that conform to the specified signature, null if there are none such. CONTRACT: do not + * modify! + */ + public Collection get(Tuple signature); + + /** + * @since 2.4 + */ + default public Map> getTimeline(Tuple signature) { + throw new UnsupportedOperationException(); + } + + /** + * This indexer will be updated whenever a Rete update is sent to the active node (or an equivalent time slot + * allotted to it). The active node is typically the indexer itself, but it can be a different node such as its + * parent. + * + * @return the active node that operates this indexer + */ + public Node getActiveNode(); + + + public Collection getListeners(); + + public void attachListener(IndexerListener listener); + + public void detachListener(IndexerListener listener); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A listener for update events concerning an Indexer. + * + * @author Gabor Bergmann + * + */ +public interface IndexerListener { + /** + * Notifies recipient that the indexer has just received an update. Contract: indexer already reflects the updated + * state. + * + * @param direction + * the direction of the update. + * @param updateElement + * the tuple that was updated. + * @param signature + * the signature of the tuple according to the indexer's mask. + * @param change + * whether this was the first inserted / last revoked update element with this particular signature. + * @since 2.4 + */ + void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp); + + Node getOwner(); +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * @author Gabor Bergmann + * @author Tamas Szabo + */ +public abstract class IndexerWithMemory extends StandardIndexer + implements Receiver, NetworkStructureChangeSensitiveNode, ResumableNode { + + protected MaskedTupleMemory memory; + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + public IndexerWithMemory(final ReteContainer reteContainer, final TupleMask mask) { + super(reteContainer, mask); + final boolean isTimely = reteContainer.isTimelyEvaluation() + && reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + memory = MaskedTupleMemory.create(mask, MemoryType.SETS, this, isTimely, isTimely && reteContainer + .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + reteContainer.registerClearable(memory); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + this.logic = createLogic(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + final boolean wasTimely = this.memory.isTimely(); + final boolean isTimely = this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + if (wasTimely != isTimely) { + final MaskedTupleMemory newMemory = MaskedTupleMemory.create(mask, MemoryType.SETS, this, + isTimely, isTimely && reteContainer.getTimelyConfiguration() + .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + newMemory.initializeWith(this.memory, Timestamp.ZERO); + memory.clear(); + memory = newMemory; + } + this.logic = createLogic(); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own + * mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + /** + * @since 2.0 + */ + public MaskedTupleMemory getMemory() { + return memory; + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + this.logic.update(direction, updateElement, timestamp); + } + + /** + * Refined version of update + * + * @since 2.4 + */ + protected abstract void update(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp); + + @Override + public void appendParent(final Supplier supplier) { + if (parent == null) { + parent = supplier; + } else { + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + "). "); + } + } + + @Override + public void removeParent(final Supplier supplier) { + if (parent == supplier) { + parent = null; + } else { + throw new IllegalArgumentException( + "Illegal RETE edge removal: the parent of " + this + " is not " + supplier); + } + } + + /** + * @since 2.4 + */ + @Override + public Collection getParents() { + return Collections.singleton(parent); + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + this.logic.resumeAt(timestamp); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + return this.memory.getResumableTimestamp(); + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * @since 2.4 + */ + public abstract void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp); + + /** + * @since 2.4 + */ + public abstract void resumeAt(final Timestamp timestamp); + + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return TIMELY; + } else { + return TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + final Iterable signatures = memory.getResumableSignatures(); + + final Map wasPresent = CollectionsFactory.createMap(); + for (final Tuple signature : signatures) { + wasPresent.put(signature, memory.isPresentAtInfinity(signature)); + } + + final Map>> signatureMap = memory.resumeAt(timestamp); + + for (final Entry>> outerEntry : signatureMap.entrySet()) { + final Tuple signature = outerEntry.getKey(); + final Map> diffMap = outerEntry.getValue(); + final boolean isPresent = memory.isPresentAtInfinity(signature); + // only send out a potential true value the first time for a given signature, then set it to false + boolean change = wasPresent.get(signature) ^ isPresent; + + for (final Entry> innerEntry : diffMap.entrySet()) { + final Tuple tuple = innerEntry.getKey(); + final Diff diffs = innerEntry.getValue(); + for (final Signed signed : diffs) { + IndexerWithMemory.this.update(signed.getDirection(), tuple, signature, change, + signed.getPayload()); + } + // change is a signature-wise flag, so it is ok to "try" to signal it for the first tuple only + change = false; + } + } + + final Timestamp nextTimestamp = memory.getResumableTimestamp(); + if (nextTimestamp != null) { + group.notifyHasMessage(mailbox, nextTimestamp); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple signature = mask.transform(update); + final boolean wasPresent = memory.isPresentAtInfinity(signature); + final Diff resultDiff = direction == Direction.INSERT + ? memory.addWithTimestamp(update, signature, timestamp) + : memory.removeWithTimestamp(update, signature, timestamp); + final boolean isPresent = memory.isPresentAtInfinity(signature); + final boolean change = wasPresent ^ isPresent; + for (final Signed signed : resultDiff) { + IndexerWithMemory.this.update(signed.getDirection(), update, signature, change, signed.getPayload()); + } + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple signature = mask.transform(update); + final boolean change = direction == Direction.INSERT ? memory.add(update, signature) + : memory.remove(update, signature); + IndexerWithMemory.this.update(direction, update, signature, change, timestamp); + } + + @Override + public void resumeAt(final Timestamp timestamp) { + // there is nothing to resume in the timeless case because we do not even care about timestamps + } + + }; + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * An indexer that allows the iteration of all retrievable tuple groups (or reduced groups). + * + * @author Gabor Bergmann + * + */ +public interface IterableIndexer extends Indexer, Iterable { + + /** + * A view consisting of exactly those signatures whose tuple group is not empty + * @since 2.0 + */ + public Iterable getSignatures(); + + /** + * @return the number of signatures whose tuple group is not empty + * @since 2.0 + */ + public int getBucketCount(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Gabor Bergmann + * + */ +public class JoinNode extends DualInputNode { + + public JoinNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) { + super(reteContainer, complementerSecondaryMask); + this.logic = createLogic(); + } + + @Override + public Tuple calibrate(final Tuple primary, final Tuple secondary) { + return unify(primary, secondary); + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Collection primaries = primarySlot.get(signature); + final Collection opposites = secondarySlot.get(signature); + if (opposites != null) { + for (final Tuple primary : primaries) { + for (final Tuple opposite : opposites) { + collector.add(unify(primary, opposite)); + } + } + } + } + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + // in the default case, all timestamps must be zero + assert Timestamp.ZERO.equals(timestamp); + + final Collection opposites = retrieveOpposites(side, signature); + + if (!coincidence) { + if (opposites != null) { + for (final Tuple opposite : opposites) { + propagateUpdate(direction, unify(side, updateElement, opposite), timestamp); + } + } + } else { + // compensate for coincidence of slots - this is the case when an Indexer is joined with itself + if (opposites != null) { + for (final Tuple opposite : opposites) { + if (opposite.equals(updateElement)) { + // handle self-joins of a single tuple separately + continue; + } + propagateUpdate(direction, unify(opposite, updateElement), timestamp); + propagateUpdate(direction, unify(updateElement, opposite), timestamp); + } + } + + // handle self-joins here + propagateUpdate(direction, unify(updateElement, updateElement), timestamp); + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Map> primaries = getTimeline(signature, primarySlot); + final Map> opposites = getTimeline(signature, secondarySlot); + if (opposites != null) { + for (final Tuple primary : primaries.keySet()) { + for (final Tuple opposite : opposites.keySet()) { + final Timeline primaryTimeline = primaries.get(primary); + final Timeline oppositeTimeline = opposites.get(opposite); + final Timeline mergedTimeline = primaryTimeline + .mergeMultiplicative(oppositeTimeline); + if (!mergedTimeline.isEmpty()) { + collector.put(unify(primary, opposite), mergedTimeline); + } + } + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + JoinNode.this.TIMELESS.pullInto(collector, flush); + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + final Indexer oppositeIndexer = getSlot(side.opposite()); + final Map> opposites = getTimeline(signature, oppositeIndexer); + + if (!coincidence) { + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + final Tuple unifiedTuple = unify(side, updateElement, opposite); + for (final Signed signed : opposites.get(opposite).asChangeSequence()) { + // TODO only consider signed timestamps that are greater or equal to timestamp + // plus compact the previous timestamps into at most one update + propagateUpdate(signed.getDirection().multiply(direction), unifiedTuple, + timestamp.max(signed.getPayload())); + } + } + } + } else { + // compensate for coincidence of slots - this is the case when an Indexer is joined with itself + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + if (opposite.equals(updateElement)) { + // handle self-joins of a single tuple separately + continue; + } + final Tuple u1 = unify(opposite, updateElement); + final Tuple u2 = unify(updateElement, opposite); + for (final Signed oppositeSigned : opposites.get(opposite).asChangeSequence()) { + final Direction updateDirection = direction.multiply(oppositeSigned.getDirection()); + final Timestamp updateTimestamp = timestamp.max(oppositeSigned.getPayload()); + propagateUpdate(updateDirection, u1, updateTimestamp); + propagateUpdate(updateDirection, u2, updateTimestamp); + } + } + } + + // handle self-join here + propagateUpdate(direction, unify(updateElement, updateElement), timestamp); + } + } + }; + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelessLogic() { + return this.TIMELESS; + } + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelyLogic() { + return this.TIMELY; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * Defines a trivial indexer that identically projects the contents of a memory-equipped node, and can therefore save + * space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents + * directly! + * + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + * @author Gabor Bergmann + */ + +public class MemoryIdentityIndexer extends IdentityIndexer { + + protected final Collection memory; + + /** + * @param reteContainer + * @param tupleWidth + * the width of the tuples of memoryNode + * @param memory + * the memory whose contents are to be identity-indexed + * @param parent + * the parent node that owns the memory + */ + public MemoryIdentityIndexer(ReteContainer reteContainer, int tupleWidth, Collection memory, + Supplier parent, Receiver activeNode, List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + protected Collection getTuples() { + return this.memory; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * Defines a trivial indexer that projects the contents of a memory-equipped node to the empty tuple, and can therefore + * save space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents + * directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class MemoryNullIndexer extends NullIndexer { + + Collection memory; + + /** + * @param reteContainer + * @param tupleWidth + * the width of the tuples of memoryNode + * @param memory + * the memory whose contents are to be null-indexed + * @param parent + * the parent node that owns the memory + */ + public MemoryNullIndexer(ReteContainer reteContainer, int tupleWidth, Collection memory, + Supplier parent, Receiver activeNode, List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + protected Collection getTuples() { + return this.memory; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Defines an abstract trivial indexer that projects the contents of some stateful node to the empty tuple, and can + * therefore save space. Can only exist in connection with a stateful store, and must be operated by another node (the + * active node). Do not attach parents directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class NullIndexer extends SpecializedProjectionIndexer { + + protected abstract Collection getTuples(); + + protected static final Tuple nullSignature = Tuples.staticArityFlatTupleOf(); + protected static final Collection nullSingleton = Collections.singleton(nullSignature); + protected static final Collection emptySet = Collections.emptySet(); + + public NullIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent, Node activeNode, + List sharedSubscriptionList) { + super(reteContainer, TupleMask.linear(0, tupleWidth), parent, activeNode, sharedSubscriptionList); + } + + @Override + public Collection get(Tuple signature) { + if (nullSignature.equals(signature)) + return isEmpty() ? null : getTuples(); + else + return null; + } + + @Override + public Collection getSignatures() { + return isEmpty() ? emptySet : nullSingleton; + } + + protected boolean isEmpty() { + return getTuples().isEmpty(); + } + + protected boolean isSingleElement() { + return getTuples().size() == 1; + } + + @Override + public Iterator iterator() { + return getTuples().iterator(); + } + + @Override + public int getBucketCount() { + return getTuples().isEmpty() ? 0 : 1; + } + + @Override + public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, + Timestamp timestamp) { + boolean radical = (direction == Direction.DELETE && isEmpty()) + || (direction == Direction.INSERT && isSingleElement()); + listener.notifyIndexerUpdate(direction, updateElement, nullSignature, radical, timestamp); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * @author Gabor Bergmann Indexer whose lifetime last until the first get() DO NOT connect to nodes! + */ +public class OnetimeIndexer extends GenericProjectionIndexer { + + public OnetimeIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer, mask); + } + + @Override + public Collection get(Tuple signature) { + if (tools.refinery.viatra.runtime.rete.util.Options.releaseOnetimeIndexers) { + reteContainer.unregisterClearable(memory); + reteContainer.unregisterNode(this); + } + return super.get(signature); + } + + @Override + public void appendParent(Supplier supplier) { + throw new UnsupportedOperationException("onetime indexer cannot have parents"); + } + + @Override + public void attachListener(IndexerListener listener) { + throw new UnsupportedOperationException("onetime indexer cannot have listeners"); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +/** + * An iterable indexer that receives updates from a node, and groups received tuples intact, i.e. it does not reduce + * tuple groups. + * + * @author Gabor Bergmann + * + */ +public interface ProjectionIndexer extends IterableIndexer { + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A specialized projection indexer that can be memory-less (relying on an external source of information). + * + *

+ * All specialized projection indexers of a single node will share the same listener list, so that notification order is + * maintained (see Bug 518434). + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class SpecializedProjectionIndexer extends StandardIndexer implements ProjectionIndexer { + + protected Node activeNode; + protected List subscriptions; + + /** + * @since 1.7 + */ + public SpecializedProjectionIndexer(final ReteContainer reteContainer, final TupleMask mask, final Supplier parent, + final Node activeNode, final List subscriptions) { + super(reteContainer, mask); + this.parent = parent; + this.activeNode = activeNode; + this.subscriptions = subscriptions; + } + + public List getSubscriptions() { + return subscriptions; + } + + @Override + public Node getActiveNode() { + return activeNode; + } + + @Override + protected void propagate(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp) { + throw new UnsupportedOperationException(); + } + + @Override + public void attachListener(final IndexerListener listener) { + super.attachListener(listener); + final CommunicationTracker tracker = this.getCommunicationTracker(); + final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener); + final ListenerSubscription subscription = new ListenerSubscription(this, proxy); + tracker.registerDependency(this, proxy.getOwner()); + // See Bug 518434 + // Must add to the first position, so that the later listeners are notified earlier. + // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite + // slot, + // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier, + // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation + subscriptions.add(0, subscription); + } + + @Override + public void detachListener(final IndexerListener listener) { + final CommunicationTracker tracker = this.getCommunicationTracker(); + // obtain the proxy before the super call would unregister the dependency + final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener); + super.detachListener(listener); + final ListenerSubscription subscription = new ListenerSubscription(this, proxy); + final boolean wasContained = subscriptions.remove(subscription); + assert wasContained; + tracker.unregisterDependency(this, proxy.getOwner()); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + final List oldSubscriptions = new ArrayList(); + oldSubscriptions.addAll(subscriptions); + subscriptions.clear(); + for (final ListenerSubscription oldSubscription : oldSubscriptions) { + // there is no need to unregister and re-register the dependency between indexer and listener + // because the owner of the listener is the same (even if it is proxified) + final CommunicationTracker tracker = this.getCommunicationTracker(); + // the subscriptions are shared, so we MUST reuse the indexer of the subscription instead of simply 'this' + final IndexerListener proxy = tracker.proxifyIndexerListener(oldSubscription.indexer, oldSubscription.listener); + final ListenerSubscription newSubscription = new ListenerSubscription(oldSubscription.indexer, proxy); + subscriptions.add(newSubscription); + } + } + + /** + * @since 2.4 + */ + public abstract void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, + Timestamp timestamp); + + /** + * Infrastructure to share subscriptions between specialized indexers of the same parent node. + * + * @author Gabor Bergmann + * @since 1.7 + */ + public static class ListenerSubscription { + protected SpecializedProjectionIndexer indexer; + protected IndexerListener listener; + + public ListenerSubscription(SpecializedProjectionIndexer indexer, IndexerListener listener) { + super(); + this.indexer = indexer; + this.listener = listener; + } + + /** + * @since 2.4 + */ + public SpecializedProjectionIndexer getIndexer() { + return indexer; + } + + /** + * @since 2.4 + */ + public IndexerListener getListener() { + return listener; + } + + /** + * Call this from parent node. + * @since 2.4 + */ + public void propagate(Direction direction, Tuple updateElement, Timestamp timestamp) { + indexer.propagateToListener(listener, direction, updateElement, timestamp); + } + + @Override + public int hashCode() { + return Objects.hash(indexer, listener); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ListenerSubscription other = (ListenerSubscription) obj; + return Objects.equals(listener, other.listener) && Objects.equals(indexer, other.indexer); + } + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.BaseNode; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * An abstract standard implementation of the Indexer interface, providing common bookkeeping functionality. + * + * @author Gabor Bergmann + * + */ +public abstract class StandardIndexer extends BaseNode implements Indexer, NetworkStructureChangeSensitiveNode { + + protected Supplier parent; + private final List originalListeners; + private final List proxyListeners; + protected TupleMask mask; + + public StandardIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.parent = null; + this.mask = mask; + this.originalListeners = CollectionsFactory.createObserverList(); + this.proxyListeners = CollectionsFactory.createObserverList(); + } + + /** + * @since 2.4 + */ + protected void propagate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) { + for (IndexerListener listener : proxyListeners) { + listener.notifyIndexerUpdate(direction, updateElement, signature, change, timestamp); + } + } + + @Override + public TupleMask getMask() { + return mask; + } + + @Override + public Supplier getParent() { + return parent; + } + + @Override + public void attachListener(IndexerListener listener) { + this.getCommunicationTracker().registerDependency(this, listener.getOwner()); + // obtain the proxy after registering the dependency because then the proxy reflects the new SCC structure + final IndexerListener proxy = this.getCommunicationTracker().proxifyIndexerListener(this, listener); + // See Bug 518434 + // Must add to the first position, so that the later listeners are notified earlier. + // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite slot, + // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier, + // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation + this.originalListeners.add(0, listener); + this.proxyListeners.add(0, proxy); + } + + @Override + public void detachListener(IndexerListener listener) { + this.originalListeners.remove(listener); + IndexerListener listenerToRemove = null; + for (final IndexerListener proxyListener : this.proxyListeners) { + if (proxyListener.getOwner() == listener.getOwner()) { + listenerToRemove = proxyListener; + break; + } + } + assert listenerToRemove != null; + this.proxyListeners.remove(listenerToRemove); + this.getCommunicationTracker().unregisterDependency(this, listener.getOwner()); + } + + @Override + public void networkStructureChanged() { + this.proxyListeners.clear(); + for (final IndexerListener original : this.originalListeners) { + this.proxyListeners.add(this.getCommunicationTracker().proxifyIndexerListener(this, original)); + } + } + + @Override + public Collection getListeners() { + return proxyListeners; + } + + @Override + public ReteContainer getContainer() { + return reteContainer; + } + + @Override + protected String toStringCore() { + return super.toStringCore() + "(" + parent + "/" + mask + ")"; + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromIndexerToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + + +} 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..b0bea8cb --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.matchers.tuple.MaskedTuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode; + +// UNFINISHED, not used yet +public class TransitiveClosureNodeIndexer extends StandardIndexer implements IterableIndexer { + private TransitiveClosureNode tcNode; + private IncSCCAlg tcAlg; + private Collection emptySet; + + public TransitiveClosureNodeIndexer(TupleMask mask, IncSCCAlg tcAlg, TransitiveClosureNode tcNode) { + super(tcNode.getContainer(), mask); + this.tcAlg = tcAlg; + this.tcNode = tcNode; + this.emptySet = Collections.emptySet(); + this.parent = tcNode; + } + + @Override + public Collection get(Tuple signature) { + if (signature.getSize() == mask.sourceWidth) { + if (mask.indices.length == 0) { + // mask ()/2 + return getSignatures(); + } else if (mask.indices.length == 1) { + Set retSet = CollectionsFactory.createSet(); + + // mask (0)/2 + if (mask.indices[0] == 0) { + Object source = signature.get(0); + for (Object target : tcAlg.getAllReachableTargets(source)) { + retSet.add(Tuples.staticArityFlatTupleOf(source, target)); + } + return retSet; + } + // mask (1)/2 + if (mask.indices[0] == 1) { + Object target = signature.get(1); + for (Object source : tcAlg.getAllReachableSources(target)) { + retSet.add(Tuples.staticArityFlatTupleOf(source, target)); + } + return retSet; + } + } else { + // mask (0,1)/2 + if (mask.indices[0] == 0 && mask.indices[1] == 1) { + Object source = signature.get(0); + Object target = signature.get(1); + Tuple singleton = Tuples.staticArityFlatTupleOf(source, target); + return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet); + } + // mask (1,0)/2 + if (mask.indices[0] == 1 && mask.indices[1] == 0) { + Object source = signature.get(1); + Object target = signature.get(0); + Tuple singleton = Tuples.staticArityFlatTupleOf(source, target); + return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet); + } + } + } + return null; + } + + @Override + public int getBucketCount() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getSignatures() { + return asTupleCollection(tcAlg.getTcRelation()); + } + + @Override + public Iterator iterator() { + return asTupleCollection(tcAlg.getTcRelation()).iterator(); + } + + private Collection asTupleCollection( + Collection> tuples) { + Set retSet = CollectionsFactory.createSet(); + for (tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple tuple : tuples) { + retSet.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget())); + } + return retSet; + } + + /** + * @since 2.4 + */ + public void propagate(Direction direction, Tuple updateElement, boolean change) { + propagate(direction, updateElement, new MaskedTuple(updateElement, mask), change, null); + } + + @Override + public Receiver getActiveNode() { + return tcNode; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.IdentityIndexer; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public class TimelyMemoryIdentityIndexer extends IdentityIndexer { + + protected final TimelyMemory memory; + + public TimelyMemoryIdentityIndexer(final ReteContainer reteContainer, final int tupleWidth, + final TimelyMemory memory, final Supplier parent, final Receiver activeNode, + final List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + public Map> getTimeline(final Tuple signature) { + final Timeline timestamp = this.memory.get(signature); + if (timestamp != null) { + return Collections.singletonMap(signature, timestamp); + } else { + return null; + } + } + + @Override + protected Collection getTuples() { + return this.memory.getTuplesAtInfinity(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index.timely; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.NullIndexer; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public class TimelyMemoryNullIndexer extends NullIndexer { + + protected final TimelyMemory memory; + + public TimelyMemoryNullIndexer(final ReteContainer reteContainer, final int tupleWidth, + final TimelyMemory memory, final Supplier parent, + final Receiver activeNode, final List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + public Map> getTimeline(final Tuple signature) { + if (nullSignature.equals(signature)) { + return isEmpty() ? null : this.memory.asMap(); + } else { + return null; + } + } + + @Override + protected Collection getTuples() { + return this.memory.getTuplesAtInfinity(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; + +/** + * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use delete and re-derive + * evaluation. + * + * @author Tamas Szabo + * @since 2.2 + */ +public class DRedReteBackendFactory extends ReteBackendFactory { + + public static final DRedReteBackendFactory INSTANCE = new DRedReteBackendFactory(); + + @Override + public IQueryBackend create(IQueryBackendContext context) { + return create(context, true, null); + } + + @Override + public int hashCode() { + return DRedReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof DRedReteBackendFactory)) { + return false; + } + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import java.util.HashMap; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A configurable hint provider that gathers hints for queries during runtime, and delegates defaults to an external hint provider. + * + * @author Gabor Bergmann + * @since 1.5 + */ +class HintConfigurator implements IQueryBackendHintProvider { + + private IQueryBackendHintProvider defaultHintProvider; + private Map storedHints = new HashMap(); + + public HintConfigurator(IQueryBackendHintProvider defaultHintProvider) { + this.defaultHintProvider = defaultHintProvider; + } + + @Override + public QueryEvaluationHint getQueryEvaluationHint(PQuery query) { + return defaultHintProvider.getQueryEvaluationHint(query).overrideBy(storedHints.get(query)); + } + + public void storeHint(PQuery query, QueryEvaluationHint hint) { + QueryEvaluationHint oldHint = storedHints.get(query); + if (oldHint == null) + storedHints.put(query, hint); + else + storedHints.put(query, oldHint.overrideBy(hint)); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class IncrementalMatcherCapability implements IMatcherCapability { + + @Override + public boolean canBeSubstitute(IMatcherCapability capability) { + /* + * TODO: for now, as we are only prepared for Rete and LS, we can assume that + * a matcher created with Rete can always be a substitute for a matcher created + * by any backend. + */ + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.viatra.runtime.rete.util.Options; + +public class ReteBackendFactory implements IQueryBackendFactory { + /** + * EXPERIMENTAL + */ + protected static final int reteThreads = 0; + + /** + * @since 2.0 + */ + public static final ReteBackendFactory INSTANCE = new ReteBackendFactory(); + + /** + * @deprecated Use the static {@link #INSTANCE} field instead + */ + @Deprecated + public ReteBackendFactory() { + } + + /** + * @since 1.5 + */ + @Override + public IQueryBackend create(IQueryBackendContext context) { + return create(context, false, null); + } + + /** + * @since 2.4 + */ + public IQueryBackend create(IQueryBackendContext context, boolean deleteAndRederiveEvaluation, + TimelyConfiguration timelyConfiguration) { + ReteEngine engine; + engine = new ReteEngine(context, reteThreads, deleteAndRederiveEvaluation, timelyConfiguration); + IQueryBackendHintProvider hintConfiguration = engine.getHintConfiguration(); + ReteRecipeCompiler compiler = new ReteRecipeCompiler( + Options.builderMethod.layoutStrategy(context, hintConfiguration), context.getLogger(), + context.getRuntimeContext().getMetaContext(), context.getQueryCacheContext(), hintConfiguration, + context.getQueryAnalyzer(), deleteAndRederiveEvaluation, timelyConfiguration); + engine.setCompiler(compiler); + return engine; + } + + @Override + public Class getBackendClass() { + return ReteEngine.class; + } + + @Override + public int hashCode() { + return ReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ReteBackendFactory)) { + return false; + } + return true; + } + + /** + * @since 1.4 + */ + @Override + public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) { + return new IncrementalMatcherCapability(); + } + + @Override + public boolean isCaching() { + return true; + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; + +/** + * @since 2.0 + * + */ +public class ReteBackendFactoryProvider implements IQueryBackendFactoryProvider { + + @Override + public IQueryBackendFactory getFactory() { + return ReteBackendFactory.INSTANCE; + } + + @Override + public boolean isSystemDefaultEngine() { + return true; + } + + @Override + public boolean isSystemDefaultCachingBackend() { + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.matcher; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.boundary.Disconnectable; +import tools.refinery.viatra.runtime.rete.boundary.ReteBoundary; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.NodeProvisioner; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * @author Gabor Bergmann + * + */ +public class ReteEngine implements IQueryBackend { + + protected Network reteNet; + protected final int reteThreads; + protected ReteBoundary boundary; + + /** + * @since 2.2 + */ + protected final boolean deleteAndRederiveEvaluation; + /** + * @since 2.4 + */ + protected final TimelyConfiguration timelyConfiguration; + + private IQueryBackendContext context; + private Logger logger; + protected IQueryRuntimeContext runtimeContext; + + protected Collection disconnectables; + + protected Map matchers; + + protected ReteRecipeCompiler compiler; + + protected final boolean parallelExecutionEnabled; // TRUE if model manipulation can go on + + private boolean disposedOrUninitialized = true; + + private HintConfigurator hintConfigurator; + + /** + * @param context + * the context of the pattern matcher, conveying all information from the outside world. + * @param reteThreads + * the number of threads to operate the RETE network with; 0 means single-threaded operation, 1 starts an + * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers. + */ + public ReteEngine(IQueryBackendContext context, int reteThreads) { + this(context, reteThreads, false, null); + } + + /** + * @since 2.4 + */ + public ReteEngine(IQueryBackendContext context, int reteThreads, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyConfiguration) { + super(); + this.context = context; + this.logger = context.getLogger(); + this.runtimeContext = context.getRuntimeContext(); + this.reteThreads = reteThreads; + this.parallelExecutionEnabled = reteThreads > 0; + this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; + this.timelyConfiguration = timelyConfiguration; + initEngine(); + this.compiler = null; + } + + /** + * @since 1.6 + */ + public IQueryBackendContext getBackendContext() { + return context; + } + + /** + * @since 2.2 + */ + public boolean isDeleteAndRederiveEvaluation() { + return this.deleteAndRederiveEvaluation; + } + + /** + * @since 2.4 + */ + public TimelyConfiguration getTimelyConfiguration() { + return this.timelyConfiguration; + } + + /** + * initializes engine components + */ + private synchronized void initEngine() { + this.disposedOrUninitialized = false; + this.disconnectables = new LinkedList(); + // this.caughtExceptions = new LinkedBlockingQueue(); + + + this.hintConfigurator = new HintConfigurator(context.getHintProvider()); + + this.reteNet = new Network(reteThreads, this); + this.boundary = new ReteBoundary(this); // prerequisite: network + + this.matchers = CollectionsFactory.createMap(); + /* this.matchersScoped = new HashMap,RetePatternMatcher>>(); */ + + // prerequisite: network, framework, boundary, disconnectables + //context.subscribeBackendForUpdates(this.boundary); + // prerequisite: boundary, disconnectables +// this.traceListener = context.subscribePatternMatcherForTraceInfluences(this); + + } + + @Override + public void flushUpdates() { + for (ReteContainer container : this.reteNet.getContainers()) { + container.deliverMessagesSingleThreaded(); + } + } + + /** + * deconstructs engine components + */ + private synchronized void deconstructEngine() { + ensureInitialized(); + reteNet.kill(); + + //context.unSubscribeBackendFromUpdates(this.boundary); + for (Disconnectable disc : disconnectables) { + disc.disconnect(); + } + + this.matchers = null; + this.disconnectables = null; + + this.reteNet = null; + this.boundary = null; + + this.hintConfigurator = null; + + // this.machineListener = new MachineListener(this); // prerequisite: + // framework, disconnectables +// this.traceListener = null; + + this.disposedOrUninitialized = true; + } + + /** + * Deconstructs the engine to get rid of it finally + */ + public void killEngine() { + deconstructEngine(); + // this.framework = null; + this.compiler = null; + this.logger = null; + } + + /** + * Resets the engine to an after-initialization phase + * + */ + public void reset() { + deconstructEngine(); + + initEngine(); + + compiler.reset(); + } + + /** + * Accesses the patternmatcher for a given pattern, constructs one if a matcher is not available yet. + * + * @pre: builder is set. + * @param query + * the pattern to be matched. + * @return a patternmatcher object that can match occurences of the given pattern. + * @throws ViatraQueryRuntimeException + * if construction fails. + */ + public synchronized RetePatternMatcher accessMatcher(final PQuery query) { + ensureInitialized(); + RetePatternMatcher matcher; + // String namespace = gtPattern.getNamespace().getName(); + // String name = gtPattern.getName(); + // String fqn = namespace + "." + name; + matcher = matchers.get(query); + if (matcher == null) { + constructionWrapper(() -> { + RecipeTraceInfo prodNode; + prodNode = boundary.accessProductionTrace(query); + + RetePatternMatcher retePatternMatcher = new RetePatternMatcher(ReteEngine.this, + prodNode); + retePatternMatcher.setTag(query); + matchers.put(query, retePatternMatcher); + return null; + }); + matcher = matchers.get(query); + } + + executeDelayedCommands(); + + return matcher; + } + + + /** + * Constructs RETE pattern matchers for a collection of patterns, if they are not available yet. Model traversal + * during the whole construction period is coalesced (which may have an effect on performance, depending on the + * matcher context). + * + * @pre: builder is set. + * @param specifications + * the patterns to be matched. + * @throws ViatraQueryRuntimeException + * if construction fails. + */ + public synchronized void buildMatchersCoalesced(final Collection specifications) { + ensureInitialized(); + constructionWrapper(() -> { + for (PQuery specification : specifications) { + boundary.accessProductionNode(specification); + } + return null; + }); + } + + /** + * @since 2.4 + */ + public T constructionWrapper(final Callable payload) { + T result = null; +// context.modelReadLock(); +// try { + if (parallelExecutionEnabled) + reteNet.getStructuralChangeLock().lock(); + try { + try { + result = runtimeContext.coalesceTraversals(() -> { + T innerResult = payload.call(); + this.executeDelayedCommands(); + return innerResult; + }); + } catch (InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause instanceof RetePatternBuildException) + throw (RetePatternBuildException) cause; + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + assert (false); + } + } finally { + if (parallelExecutionEnabled) + reteNet.getStructuralChangeLock().unlock(); + reteNet.waitForReteTermination(); + } +// } finally { +// context.modelReadUnLock(); +// } + return result; + } + + // /** + // * Accesses the patternmatcher for a given pattern with additional scoping, constructs one if + // * a matcher is not available yet. + // * + // * @param gtPattern + // * the pattern to be matched. + // * @param additionalScopeMap + // * additional, optional scopes for the symbolic parameters + // * maps the position of the symbolic parameter to its additional scope (if any) + // * @pre: scope.parent is non-root, i.e. this is a nontrivial constraint + // * use the static method RetePatternMatcher.buildAdditionalScopeMap() to create from PatternCallSignature + // * @return a patternmatcher object that can match occurences of the given + // * pattern. + // * @throws PatternMatcherCompileTimeException + // * if construction fails. + // */ + // public synchronized RetePatternMatcher accessMatcherScoped(PatternDescription gtPattern, Map + // additionalScopeMap) + // throws PatternMatcherCompileTimeException { + // if (additionalScopeMap.isEmpty()) return accessMatcher(gtPattern); + // + // RetePatternMatcher matcher; + // + // Map, RetePatternMatcher> scopes = matchersScoped.get(gtPattern); + // if (scopes == null) { + // scopes = new HashMap, RetePatternMatcher>(); + // matchersScoped.put(gtPattern, scopes); + // } + // + // matcher = scopes.get(additionalScopeMap); + // if (matcher == null) { + // context.modelReadLock(); + // try { + // reteNet.getStructuralChangeLock().lock(); + // try { + // Address prodNode; + // prodNode = boundary.accessProductionScoped(gtPattern, additionalScopeMap); + // + // matcher = new RetePatternMatcher(this, prodNode); + // scopes.put(additionalScopeMap, matcher); + // } finally { + // reteNet.getStructuralChangeLock().unlock(); + // } + // } finally { + // context.modelReadUnLock(); + // } + // // reteNet.flushUpdates(); + // } + // + // return matcher; + // } + + /** + * Returns an indexer that groups the contents of this Production node by their projections to a given mask. + * Designed to be called by a RetePatternMatcher. + * + * @param production + * the production node to be indexed. + * @param mask + * the mask that defines the projection. + * @return the Indexer. + */ + synchronized Indexer accessProjection(RecipeTraceInfo production, TupleMask mask) { + ensureInitialized(); + NodeProvisioner nodeProvisioner = reteNet.getHeadContainer().getProvisioner(); + Indexer result = nodeProvisioner.peekProjectionIndexer(production, mask); + if (result == null) { + result = constructionWrapper(() -> + nodeProvisioner.accessProjectionIndexerOnetime(production, mask) + ); + } + + return result; + } + + // /** + // * Retrieves the patternmatcher for a given pattern fqn, returns null if + // the matching network hasn't been constructed yet. + // * + // * @param fqn the fully qualified name of the pattern to be matched. + // * @return the previously constructed patternmatcher object that can match + // occurences of the given pattern, or null if it doesn't exist. + // */ + // public RetePatternMatcher getMatcher(String fqn) + // { + // RetePatternMatcher matcher = matchersByFqn.get(fqn); + // if (matcher == null) + // { + // Production prodNode = boundary.getProduction(fqn); + // + // matcher = new RetePatternMatcher(this, prodNode); + // matchersByFqn.put(fqn, matcher); + // } + // + // return matcher; + // } + + /** + * @since 2.3 + */ + public void executeDelayedCommands() { + for (final ReteContainer container : this.reteNet.getContainers()) { + container.executeDelayedCommands(); + } + } + + /** + * Waits until the pattern matcher is in a steady state and output can be retrieved. + */ + public void settle() { + ensureInitialized(); + reteNet.waitForReteTermination(); + } + + /** + * Waits until the pattern matcher is in a steady state and output can be retrieved. When steady state is reached, a + * retrieval action is executed before the steady state ceases. + * + * @param action + * the action to be run when reaching the steady-state. + */ + public void settle(Runnable action) { + ensureInitialized(); + reteNet.waitForReteTermination(action); + } + + // /** + // * @return the framework + // */ + // public IFramework getFramework() { + // return framework.get(); + // } + + /** + * @return the reteNet + */ + public Network getReteNet() { + ensureInitialized(); + return reteNet; + } + + /** + * @return the boundary + */ + public ReteBoundary getBoundary() { + ensureInitialized(); + return boundary; + } + + // /** + // * @return the pattern matcher builder + // */ + // public IRetePatternBuilder getBuilder() { + // return builder; + // } + + /** + * @param builder + * the pattern matcher builder to set + */ + public void setCompiler(ReteRecipeCompiler builder) { + ensureInitialized(); + this.compiler = builder; + } + +// /** +// * @return the manipulationListener +// */ +// public IManipulationListener getManipulationListener() { +// ensureInitialized(); +// return manipulationListener; +// } + +// /** +// * @return the traceListener +// */ +// public IPredicateTraceListener geTraceListener() { +// ensureInitialized(); +// return traceListener; +// } + + /** + * @param disc + * the new Disconnectable adapter. + */ + public void addDisconnectable(Disconnectable disc) { + ensureInitialized(); + disconnectables.add(disc); + } + + /** + * @return the parallelExecutionEnabled + */ + public boolean isParallelExecutionEnabled() { + return parallelExecutionEnabled; + } + + + public Logger getLogger() { + ensureInitialized(); + return logger; + } + + public IQueryRuntimeContext getRuntimeContext() { + ensureInitialized(); + return runtimeContext; + } + + public ReteRecipeCompiler getCompiler() { + ensureInitialized(); + return compiler; + } + + // /** + // * For internal use only: logs exceptions occurring during term evaluation inside the RETE net. + // * @param e + // */ + // public void logEvaluatorException(Throwable e) { + // try { + // caughtExceptions.put(e); + // } catch (InterruptedException e1) { + // logEvaluatorException(e); + // } + // } + // /** + // * Polls the exceptions caught and logged during term evaluation by this RETE engine. + // * Recommended usage: iterate polling until null is returned. + // * + // * @return the next caught exception, or null if there are no more. + // */ + // public Throwable getNextLoggedEvaluatorException() { + // return caughtExceptions.poll(); + // } + + void ensureInitialized() { + if (disposedOrUninitialized) + throw new IllegalStateException("Trying to use a Rete engine that has been disposed or has not yet been initialized."); + + } + + @Override + public IQueryResultProvider getResultProvider(PQuery query) { + return accessMatcher(query); + } + + /** + * @since 1.4 + */ + @Override + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) { + hintConfigurator.storeHint(query, hints); + return accessMatcher(query); + } + + @Override + public IQueryResultProvider peekExistingResultProvider(PQuery query) { + ensureInitialized(); + return matchers.get(query); + } + + @Override + public void dispose() { + killEngine(); + } + + @Override + public boolean isCaching() { + return true; + } + + /** + * @since 1.5 + * @noreference Internal API, subject to change + */ + public IQueryBackendHintProvider getHintConfiguration() { + return hintConfigurator; + } + + @Override + public IQueryBackendFactory getFactory() { + return ReteBackendFactory.INSTANCE; + } + +} 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..1f380b45 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java @@ -0,0 +1,462 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.single.CallbackNode; +import tools.refinery.viatra.runtime.rete.single.TransformerNode; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Gabor Bergmann + * + */ +public class RetePatternMatcher extends TransformerNode implements IQueryResultProvider { + + protected ReteEngine engine; + protected IQueryRuntimeContext context; + protected ProductionNode productionNode; + protected RecipeTraceInfo productionNodeTrace; + protected Map posMapping; + protected Map taggedChildren = CollectionsFactory.createMap(); + protected boolean connected = false; // is rete-wise connected to the + // production node? + + /** + * @param productionNode + * a production node that matches this pattern without any parameter bindings + * @pre: Production must be local to the head container + */ + public RetePatternMatcher(ReteEngine engine, RecipeTraceInfo productionNodeTrace) { + super(engine.getReteNet().getHeadContainer()); + this.engine = engine; + this.context = engine.getRuntimeContext(); + this.productionNodeTrace = productionNodeTrace; + final Address productionAddress = reteContainer.getProvisioner() + .getOrCreateNodeByRecipe(productionNodeTrace); + if (!reteContainer.isLocal(productionAddress)) + throw new IllegalArgumentException("@pre: Production must be local to the head container"); + this.productionNode = (ProductionNode) reteContainer.resolveLocal(productionAddress); + this.posMapping = this.productionNode.getPosMapping(); + this.reteContainer.getCommunicationTracker().registerDependency(this.productionNode, this); + } + + /** + * @since 1.6 + */ + public ProductionNode getProductionNode() { + return productionNode; + } + + public Tuple matchOneRandomly(Object[] inputMapping, boolean[] fixed) { + List allMatches = matchAll(inputMapping, fixed).collect(Collectors.toList()); + if (allMatches == null || allMatches.isEmpty()) + return null; + else + return allMatches.get((int) (Math.random() * allMatches.size())); + } + + /** + * @since 2.0 + */ + public Stream matchAll(Object[] inputMapping, boolean[] fixed) { + // retrieving the projection + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return matchAll(mask, inputSignature); + + } + + /** + * @since 2.0 + */ + public Stream matchAll(TupleMask mask, ITuple inputSignature) { + AllMatchFetcher fetcher = new AllMatchFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + return fetcher.getMatches(); + + } + + /** + * @since 2.0 + */ + public Optional matchOne(Object[] inputMapping, boolean[] fixed) { + // retrieving the projection + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return matchOne(mask, inputSignature); + } + + /** + * @since 2.0 + */ + public Optional matchOne(TupleMask mask, ITuple inputSignature) { + SingleMatchFetcher fetcher = new SingleMatchFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + return Optional.ofNullable(fetcher.getMatch()); + } + + /** + * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. + * + * @return the number of occurrences + */ + public int count(Object[] inputMapping, boolean[] fixed) { + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return count(mask, inputSignature); + } + + /** + * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. + * + * @return the number of occurrences + * @since 1.7 + */ + public int count(TupleMask mask, ITuple inputSignature) { + CountFetcher fetcher = new CountFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + + return fetcher.getCount(); + } + + /** + * Counts the number of distinct tuples attainable from the match set by projecting match tuples according to the given mask. + * + * + * @return the size of the projection + * @since 2.1 + */ + public int projectionSize(TupleMask groupMask) { + ProjectionSizeFetcher fetcher = new ProjectionSizeFetcher( + (IterableIndexer) engine.accessProjection(productionNodeTrace, groupMask)); + engine.reteNet.waitForReteTermination(fetcher); + + return fetcher.getSize(); + } + + /** + * Connects a new external receiver that will receive update notifications from now on. The receiver will + * practically connect to the production node, the added value is unwrapping the updates for external use. + * + * @param synchronize + * if true, the contents of the production node will be inserted into the receiver after the connection + * is established. + */ + public synchronized void connect(Receiver receiver, boolean synchronize) { + if (!connected) { // connect to the production node as a RETE-child + reteContainer.connect(productionNode, this); + connected = true; + } + if (synchronize) + reteContainer.connectAndSynchronize(this, receiver); + else + reteContainer.connect(this, receiver); + } + + /** + * Connects a new external receiver that will receive update notifications from now on. The receiver will + * practically connect to the production node, the added value is unwrapping the updates for external use. + * + * The external receiver will be disconnectable later based on its tag. + * + * @param tag + * an identifier to recognize the child node by. + * + * @param synchronize + * if true, the contents of the production node will be inserted into the receiver after the connection + * is established. + * + */ + public synchronized void connect(Receiver receiver, Object tag, boolean synchronize) { + taggedChildren.put(tag, receiver); + connect(receiver, synchronize); + } + + /** + * Disconnects a child node. + */ + public synchronized void disconnect(Receiver receiver) { + reteContainer.disconnect(this, receiver); + } + + /** + * Disconnects the child node that was connected by specifying the given tag. + * + * @return if a child node was found registered with this tag. + */ + public synchronized boolean disconnectByTag(Object tag) { + final Receiver receiver = taggedChildren.remove(tag); + final boolean found = receiver != null; + if (found) + disconnect(receiver); + return found; + } + + @Override + protected Tuple transform(Tuple input) { + return context.unwrapTuple(input); + } + + abstract class AbstractMatchFetcher implements Runnable { + Indexer indexer; + Tuple signature; + + public AbstractMatchFetcher(Indexer indexer, Tuple signature) { + super(); + this.indexer = indexer; + this.signature = signature; + } + + @Override + public void run() { + fetch(indexer.get(signature)); + } + + protected abstract void fetch(Collection matches); + + } + + class AllMatchFetcher extends AbstractMatchFetcher { + + public AllMatchFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + Stream matches = null; + + public Stream getMatches() { + return matches; + } + + @Override + protected void fetch(Collection matches) { + if (matches == null) + this.matches = Stream.of(); + else { + this.matches = matches.stream().map(context::unwrapTuple); + } + + } + + } + + class SingleMatchFetcher extends AbstractMatchFetcher { + + public SingleMatchFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + Tuple match = null; + + public Tuple getMatch() { + return match; + } + + @Override + protected void fetch(Collection matches) { + if (matches != null && !matches.isEmpty()) + match = context.unwrapTuple(matches.iterator().next()); + } + + // public void run() { + // Collection unscopedMatches = indexer.get(signature); + // + // // checking scopes + // if (unscopedMatches != null) { + // for (Tuple um : /* productionNode */unscopedMatches) { + // match = inputConnector.unwrapTuple(um); + // return; + // + // // Tuple ps = inputConnector.unwrapTuple(um); + // // boolean ok = true; + // // if (!ignoreScope) for (int k = 0; (k < ps.getSize()) && ok; k++) { + // // if (pcs[k].getParameterMode() == ParameterMode.INPUT) { + // // // ok = ok && (inputMapping[k]==ps.elements[k]); + // // // should now be true + // // } else // ParameterMode.OUTPUT + // // { + // // IEntity scopeParent = (IEntity) pcs[k].getParameterScope().getParent(); + // // Integer containmentMode = pcs[k].getParameterScope().getContainmentMode(); + // // if (containmentMode == Scope.BELOW) + // // ok = ok && ((IModelElement) ps.get(k)).isBelowNamespace(scopeParent); + // // else + // // /* case Scope.IN: */ + // // ok = ok && scopeParent.equals(((IModelElement) ps.get(k)).getNamespace()); + // // // note: getNamespace returns null instead of the + // // // (imaginary) modelspace root entity for top level + // // // elements; + // // // this is not a problem here as Scope.IN implies + // // // scopeParent != root. + // // + // // } + // // } + // // + // // if (ok) { + // // reteMatching = new ReteMatching(ps, posMapping); + // // return; + // // } + // } + // } + // + // } + + } + + class CountFetcher extends AbstractMatchFetcher { + + public CountFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + int count = 0; + + public int getCount() { + return count; + } + + @Override + protected void fetch(Collection matches) { + count = matches == null ? 0 : matches.size(); + } + + } + + class ProjectionSizeFetcher implements Runnable { + IterableIndexer indexer; + int size = 0; + + public ProjectionSizeFetcher(IterableIndexer indexer) { + super(); + this.indexer = indexer; + } + + @Override + public void run() { + size = indexer.getBucketCount(); + } + + public int getSize() { + return size; + } + + } + + private boolean[] notNull(Object[] parameters) { + boolean[] notNull = new boolean[parameters.length]; + for (int i = 0; i < parameters.length; ++i) + notNull[i] = parameters[i] != null; + return notNull; + } + + + + @Override + public boolean hasMatch(Object[] parameters) { + return countMatches(parameters) > 0; + } + + @Override + public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) { + return count(parameterSeedMask, parameters) > 0; + } + + @Override + public int countMatches(Object[] parameters) { + return count(parameters, notNull(parameters)); + } + + @Override + public int countMatches(TupleMask parameterSeedMask, ITuple parameters) { + return count(parameterSeedMask, parameters); + } + + + @Override + public Optional estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) { + return Optional.of((long)projectionSize(groupMask)); // always accurate + } + + @Override + public Optional getOneArbitraryMatch(Object[] parameters) { + return matchOne(parameters, notNull(parameters)); + } + + @Override + public Optional getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) { + return matchOne(parameterSeedMask, parameters); + } + + @Override + public Stream getAllMatches(Object[] parameters) { + return matchAll(parameters, notNull(parameters)); + } + + @Override + public Stream getAllMatches(TupleMask parameterSeedMask, ITuple parameters) { + return matchAll(parameterSeedMask, parameters); + } + + @Override + public IQueryBackend getQueryBackend() { + return engine; + } + + @Override + public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow) { + // As a listener is added as a delayed command, they should be executed to make sure everything is consistent on + // return, see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=562369 + engine.constructionWrapper(() -> { + final CallbackNode callbackNode = new CallbackNode(this.reteContainer, listener); + connect(callbackNode, listenerTag, fireNow); + return null; + }); + } + + @Override + public void removeUpdateListener(Object listenerTag) { + engine.constructionWrapper(() -> { + disconnectByTag(listenerTag); + return null; + }); + } + + public Indexer getInternalIndexer(TupleMask mask) { + return engine.accessProjection(productionNodeTrace, mask); + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +/** + * Configuration of timely evaluation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class TimelyConfiguration { + + private final AggregatorArchitecture aggregatorArchitecture; + private final TimelineRepresentation timelineRepresentation; + + public TimelyConfiguration(final TimelineRepresentation timelineRepresentation, + final AggregatorArchitecture aggregatorArchitecture) { + this.aggregatorArchitecture = aggregatorArchitecture; + this.timelineRepresentation = timelineRepresentation; + } + + public AggregatorArchitecture getAggregatorArchitecture() { + return aggregatorArchitecture; + } + + public TimelineRepresentation getTimelineRepresentation() { + return timelineRepresentation; + } + + public enum AggregatorArchitecture { + /** + * Aggregands are copied over from lower timestamps to higher timestamps. + */ + PARALLEL, + + /** + * Aggregands are only present at the timestamp where they are inserted at. + * Only aggregate results are pushed towards higher timestamps during folding. + */ + SEQUENTIAL + } + + public enum TimelineRepresentation { + /** + * Only the first moment (timestamp) of appearance is maintained per tuple. + */ + FIRST_ONLY, + + /** + * Complete timeline (series of appearance & disappearance) is maintained per tuple. + */ + FAITHFUL + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; + +/** + * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use non-scattered timely + * evaluation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class TimelyReteBackendFactory extends ReteBackendFactory { + + private final TimelyConfiguration configuration; + + public static final TimelyReteBackendFactory FIRST_ONLY_SEQUENTIAL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FIRST_ONLY_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.PARALLEL)); + public static final TimelyReteBackendFactory FAITHFUL_SEQUENTIAL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FAITHFUL_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.PARALLEL)); + + public TimelyReteBackendFactory(final TimelyConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public IQueryBackend create(final IQueryBackendContext context) { + return create(context, false, configuration); + } + + @Override + public int hashCode() { + return TimelyReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TimelyReteBackendFactory)) { + return false; + } + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.LinkedList; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Gabor Bergmann + * + * A bag is a container that tuples can be dumped into. Does NOT propagate updates! Optimized for small contents + * size OR positive updates only. + */ +public class Bag extends SimpleReceiver { + + public Collection contents; + + public Bag(ReteContainer reteContainer) { + super(reteContainer); + contents = new LinkedList(); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + if (direction == Direction.INSERT) + contents.add(updateElement); + else + contents.remove(updateElement); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Node that always contains a single constant Tuple + * + * @author Gabor Bergmann + */ +public class ConstantNode extends StandardNode { + + protected Tuple constant; + + /** + * @param constant + * will be wrapped using {@link IQueryRuntimeContext#wrapTuple(Tuple)} + */ + public ConstantNode(ReteContainer reteContainer, Tuple constant) { + super(reteContainer); + this.constant = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapTuple(constant); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + collector.add(constant); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + collector.put(constant, Timestamp.INSERT_AT_ZERO_TIMELINE); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Default configuration for DeltaMonitor. + * + * @author Gabor Bergmann + * + */ +public class DefaultDeltaMonitor extends DeltaMonitor { + + /** + * @param reteContainer + */ + public DefaultDeltaMonitor(ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * @param network + */ + public DefaultDeltaMonitor(Network network) { + super(network.getHeadContainer()); + } + + @Override + public Tuple statelessConvert(Tuple tuple) { + return tuple; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A monitoring object that connects to the rete network as a receiver to reflect changes since an arbitrary state + * acknowledged by the client. Match tuples are represented by a type MatchType. + * + *

+ * Usage. If a new matching is found, it appears in the matchFoundEvents collection, and disappears when that + * particular matching cannot be found anymore. If the event of finding a match has been processed by the client, it can + * be removed manually. In this case, when a previously found matching is lost, the Tuple will appear in the + * matchLostEvents collection, and disappear upon finding the same matching again. "Matching lost" events can also be + * acknowledged by removing a Tuple from the collection. If the matching is found once again, it will return to + * matchFoundEvents. + * + *

+ * Technical notes. Does NOT propagate updates! + * + * By overriding statelessConvert(), results can be stored to a MatchType. MatchType must provide equals() and + * hashCode() reflecting its contents. The default implementation (DefaultDeltaMonitor) uses Tuple as MatchType. + * + * By overriding statelessFilter(), some tuples can be filtered. + * + * @author Gabor Bergmann + * + */ +public abstract class DeltaMonitor extends SimpleReceiver implements Clearable { + + /** + * matches that are newly found + */ + public Collection matchFoundEvents; + /** + * matches that are newly lost + */ + public Collection matchLostEvents; + + /** + * @param reteContainer + */ + public DeltaMonitor(ReteContainer reteContainer) { + super(reteContainer); + matchFoundEvents = new LinkedHashSet(); + matchLostEvents = new LinkedHashSet(); + reteContainer.registerClearable(this); + } + + // /** + // * Build a delta monitor into the head container of the network. + // * + // * @param network + // */ + // public DeltaMonitor(Network network) { + // this(network.getHeadContainer()); + // } + + /** + * Override this method to provide a lightweight, stateless filter on the tuples + * + * @param tuple + * the occurrence that is to be filtered + * @return true if this tuple should be monitored, false if ignored + */ + public boolean statelessFilter(Tuple tuple) { + return true; + } + + public abstract MatchType statelessConvert(Tuple tuple); + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + if (statelessFilter(updateElement)) { + MatchType match = statelessConvert(updateElement); + if (direction == Direction.INSERT) { + if (!matchLostEvents.remove(match)) // either had before but + // lost + matchFoundEvents.add(match); // or brand-new + } else // revoke + { + if (!matchFoundEvents.remove(match)) // either never found + // in the first + // place + matchLostEvents.add(match); // or newly lost + } + } + } + + @Override + public void clear() { + matchFoundEvents.clear(); + matchLostEvents.clear(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.Collections; + +import tools.refinery.viatra.runtime.rete.network.BaseNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Bergmann Gabor + * + */ +public abstract class SimpleReceiver extends BaseNode implements Receiver { + + protected Supplier parent = null; + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + + /** + * @param reteContainer + */ + public SimpleReceiver(ReteContainer reteContainer) { + super(reteContainer); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. + * Subclasses may override this method to provide their own mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + @Override + public void appendParent(Supplier supplier) { + if (parent == null) + parent = supplier; + else + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + + ") as it is not a Uniqueness Enforcer Node. "); + } + + @Override + public void removeParent(Supplier supplier) { + if (parent == supplier) + parent = null; + else + throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not " + + supplier); + } + + @Override + public Collection getParents() { + if (parent == null) + return Collections.emptySet(); + else + return Collections.singleton(parent); + } + + /** + * Disconnects this node from the network. Can be called publicly. + * + * @pre: child nodes, if any, must already be disconnected. + */ + public void disconnectFromNetwork() { + if (parent != null) + reteContainer.disconnect(parent, this); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import tools.refinery.viatra.runtime.rete.traceability.PatternTraceInfo; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Base implementation for a Rete node. + * + * @author Bergmann Gabor + * + */ +public abstract class BaseNode implements Node { + + protected ReteContainer reteContainer; + protected long nodeId; + protected Object tag; + protected Set traceInfos; + + /** + * @param reteContainer + * the container to create this node in + */ + public BaseNode(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + this.nodeId = reteContainer.registerNode(this); + this.traceInfos = new HashSet(); + } + + @Override + public String toString() { + if (tag != null) + return toStringCore() + "->" + getTraceInfoPatternsEnumerated() + "{" + tag.toString() + "}"; + else + return toStringCore() + "->" + getTraceInfoPatternsEnumerated(); + } + + /** + * clients should override this to append before the tag / trace indicators + */ + protected String toStringCore() { + return "[" + nodeId + "]" + getClass().getSimpleName(); + } + + @Override + public ReteContainer getContainer() { + return reteContainer; + } + + @Override + public long getNodeId() { + return nodeId; + } + + @Override + public Object getTag() { + return tag; + } + + @Override + public void setTag(Object tag) { + this.tag = tag; + } + + @Override + public Set getTraceInfos() { + return Collections.unmodifiableSet(traceInfos); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + traceInfos.add(traceInfo); + traceInfo.assignNode(this); + } + + @Override + public void acceptPropagatedTraceInfo(TraceInfo traceInfo) { + assignTraceInfo(traceInfo); + } + + /** + * Descendants should use this in e.g. logging + */ + protected String getTraceInfoPatternsEnumerated() { + TreeSet patternNames = new TreeSet(); + for (TraceInfo trInfo : traceInfos) { + if (trInfo instanceof PatternTraceInfo) { + final String pName = ((PatternTraceInfo) trInfo).getPatternName(); + patternNames.add(pName); + } + } + return patternNames.toString(); + } + +} \ 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..6606a75d --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.aggregation.IndexerBasedAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Class responsible for connecting freshly instantiating Rete nodes to their parents. + * + * @author Bergmann Gabor + * + */ +class ConnectionFactory { + ReteContainer reteContainer; + + public ConnectionFactory(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + } + + // TODO move to node implementation instead? + private boolean isStateful(ReteNodeRecipe recipe) { + return recipe instanceof ProjectionIndexerRecipe || recipe instanceof IndexerBasedAggregatorRecipe + || recipe instanceof SingleColumnAggregatorRecipe || recipe instanceof ExpressionEnforcerRecipe + || recipe instanceof TransitiveClosureRecipe || recipe instanceof ProductionRecipe + || recipe instanceof UniquenessEnforcerRecipe || recipe instanceof RelationEvaluationRecipe; + + } + + /** + * PRE: nodes for parent recipes must already be created and registered + *

+ * PRE: must not be an input node (for which {@link InputConnector} is responsible) + */ + public void connectToParents(RecipeTraceInfo recipeTrace, Node freshNode) { + final ReteNodeRecipe recipe = recipeTrace.getRecipe(); + if (recipe instanceof ConstantRecipe) { + // NO-OP + } else if (recipe instanceof InputRecipe) { + throw new IllegalArgumentException( + ConnectionFactory.class.getSimpleName() + " not intended for input connection: " + recipe); + } else if (recipe instanceof SingleParentNodeRecipe) { + final Receiver receiver = (Receiver) freshNode; + ReteNodeRecipe parentRecipe = ((SingleParentNodeRecipe) recipe).getParent(); + connectToParent(recipe, receiver, parentRecipe); + } else if (recipe instanceof RelationEvaluationRecipe) { + List parentRecipes = ((MultiParentNodeRecipe) recipe).getParents(); + List parentSuppliers = new ArrayList(); + for (final ReteNodeRecipe parentRecipe : parentRecipes) { + parentSuppliers.add(getSupplierForRecipe(parentRecipe)); + } + ((RelationEvaluatorNode) freshNode).connectToParents(parentSuppliers); + } else if (recipe instanceof BetaRecipe) { + final DualInputNode beta = (DualInputNode) freshNode; + final ArrayList parentTraces = new ArrayList( + recipeTrace.getParentRecipeTraces()); + Slots slots = avoidActiveNodeConflict(parentTraces.get(0), parentTraces.get(1)); + beta.connectToIndexers(slots.primary, slots.secondary); + } else if (recipe instanceof IndexerBasedAggregatorRecipe) { + final IndexerBasedAggregatorNode aggregator = (IndexerBasedAggregatorNode) freshNode; + final IndexerBasedAggregatorRecipe aggregatorRecipe = (IndexerBasedAggregatorRecipe) recipe; + aggregator.initializeWith((ProjectionIndexer) resolveIndexer(aggregatorRecipe.getParent())); + } else if (recipe instanceof MultiParentNodeRecipe) { + final Receiver receiver = (Receiver) freshNode; + List parentRecipes = ((MultiParentNodeRecipe) recipe).getParents(); + for (ReteNodeRecipe parentRecipe : parentRecipes) { + connectToParent(recipe, receiver, parentRecipe); + } + } + } + + private Indexer resolveIndexer(final IndexerRecipe indexerRecipe) { + final Address address = reteContainer.getNetwork().getExistingNodeByRecipe(indexerRecipe); + return (Indexer) reteContainer.resolveLocal(address); + } + + private void connectToParent(ReteNodeRecipe recipe, Receiver freshNode, ReteNodeRecipe parentRecipe) { + final Supplier parentSupplier = getSupplierForRecipe(parentRecipe); + + // special synch + if (freshNode instanceof ReinitializedNode) { + Collection tuples = new ArrayList(); + parentSupplier.pullInto(tuples, true); + ((ReinitializedNode) freshNode).reinitializeWith(tuples); + reteContainer.connect(parentSupplier, freshNode); + } else { // default case + // stateless nodes do not have to be synced with contents UNLESS they already have children (recursive + // corner case) + if (isStateful(recipe) + || ((freshNode instanceof Supplier) && !((Supplier) freshNode).getReceivers().isEmpty())) { + reteContainer.connectAndSynchronize(parentSupplier, freshNode); + } else { + // stateless node, no synch + reteContainer.connect(parentSupplier, freshNode); + } + } + } + + private Supplier getSupplierForRecipe(ReteNodeRecipe recipe) { + @SuppressWarnings("unchecked") + final Address parentAddress = (Address) reteContainer.getNetwork() + .getExistingNodeByRecipe(recipe); + final Supplier supplier = reteContainer.getProvisioner().asSupplier(parentAddress); + return supplier; + } + + /** + * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of + * the two indexers is supported. + * + * @return a replacement for the secondary Indexers, if needed + */ + private Slots avoidActiveNodeConflict(final RecipeTraceInfo primarySlot, final RecipeTraceInfo secondarySlot) { + Slots result = new Slots() { + { + primary = (IterableIndexer) resolveIndexer((ProjectionIndexerRecipe) primarySlot.getRecipe()); + secondary = resolveIndexer((IndexerRecipe) secondarySlot.getRecipe()); + } + }; + if (activeNodeConflict(result.primary, result.secondary)) + if (result.secondary instanceof IterableIndexer) + result.secondary = resolveActiveIndexer(secondarySlot); + else + result.primary = (IterableIndexer) resolveActiveIndexer(primarySlot); + return result; + } + + private Indexer resolveActiveIndexer(final RecipeTraceInfo inactiveIndexerTrace) { + final RecipeTraceInfo activeIndexerTrace = reteContainer.getProvisioner() + .accessActiveIndexer(inactiveIndexerTrace); + reteContainer.getProvisioner().getOrCreateNodeByRecipe(activeIndexerTrace); + return resolveIndexer((ProjectionIndexerRecipe) activeIndexerTrace.getRecipe()); + } + + private static class Slots { + IterableIndexer primary; + Indexer secondary; + } + + /** + * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of + * the two indexers is supported. + * + * @return true if there is a conflict of active nodes. + */ + private boolean activeNodeConflict(Indexer primarySlot, Indexer secondarySlot) { + return !primarySlot.equals(secondarySlot) && primarySlot.getActiveNode().equals(secondarySlot.getActiveNode()); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public interface IGroupable { + + /** + * @return the current group of the mailbox + * @since 1.7 + */ + CommunicationGroup getCurrentGroup(); + + /** + * Sets the current group of the mailbox + * @since 1.7 + */ + void setCurrentGroup(CommunicationGroup group); + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * @author Gabor Bergmann + * + */ +public class Network { + final int threads; + + protected ArrayList containers; + ReteContainer headContainer; + private int firstContainer = 0; + private int nextContainer = 0; + + // the following fields exist only if threads > 0 + protected Map globalTerminationCriteria = null; + protected Map reportedClocks = null; + protected Lock updateLock = null; // grab during normal update operations + protected Lock structuralChangeLock = null; // grab if the network structure + // is to + // be changed + + // Knowledge of the outside world + private ReteEngine engine; + protected NodeFactory nodeFactory; + protected InputConnector inputConnector; + + // Node and recipe administration + // incl. addresses for existing nodes by recipe (where available) + // Maintained by NodeProvisioner of each container + Map> nodesByRecipe = CollectionsFactory.createMap(); + Set recipeTraces = CollectionsFactory.createSet(); + + /** + * @throws IllegalStateException + * if no node has been constructed for the recipe + */ + public synchronized Address getExistingNodeByRecipe(ReteNodeRecipe recipe) { + final Address node = nodesByRecipe.get(recipe); + if (node == null) + throw new IllegalStateException(String.format("Rete node for recipe %s not constructed yet.", recipe)); + return node; + } + + /** + * @return null if no node has been constructed for the recipe + */ + public synchronized Address getNodeByRecipeIfExists(ReteNodeRecipe recipe) { + final Address node = nodesByRecipe.get(recipe); + return node; + } + + /** + * @param threads + * the number of threads to operate the network with; 0 means single-threaded operation, 1 starts an + * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers. + */ + public Network(int threads, ReteEngine engine) { + super(); + this.threads = threads; + this.engine = engine; + this.inputConnector = new InputConnector(this); + this.nodeFactory = new NodeFactory(engine.getLogger()); + + containers = new ArrayList(); + firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD + nextContainer = firstContainer; + + if (threads > 0) { + globalTerminationCriteria = CollectionsFactory.createMap(); + reportedClocks = CollectionsFactory.createMap(); + ReadWriteLock rwl = new ReentrantReadWriteLock(); + updateLock = rwl.readLock(); + structuralChangeLock = rwl.writeLock(); + for (int i = 0; i < threads; ++i) + containers.add(new ReteContainer(this, true)); + } else + containers.add(new ReteContainer(this, false)); + + headContainer = containers.get(0); + } + + /** + * Kills this Network along with all containers and message consumption cycles. + */ + public void kill() { + for (ReteContainer container : containers) { + container.kill(); + } + containers.clear(); + } + + /** + * Returns the head container, that is guaranteed to reside in the same JVM as the Network object. + */ + public ReteContainer getHeadContainer() { + return headContainer; + } + + /** + * Returns the next container in round-robin fashion. Configurable not to yield head container. + */ + public ReteContainer getNextContainer() { + if (nextContainer >= containers.size()) + nextContainer = firstContainer; + return containers.get(nextContainer++); + } + + /** + * Internal message delivery method. + * + * @pre threads > 0 + */ + private void sendUpdate(Address receiver, Direction direction, Tuple updateElement) { + ReteContainer affectedContainer = receiver.getContainer(); + synchronized (globalTerminationCriteria) { + long newCriterion = affectedContainer.sendUpdateToLocalAddress(receiver, direction, updateElement); + terminationCriterion(affectedContainer, newCriterion); + } + } + + /** + * Internal message delivery method for single-threaded operation + * + * @pre threads == 0 + */ + private void sendUpdateSingleThreaded(Address receiver, Direction direction, + Tuple updateElement) { + ReteContainer affectedContainer = receiver.getContainer(); + affectedContainer.sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); + } + + /** + * Internal message delivery method. + * + * @pre threads > 0 + */ + private void sendUpdates(Address receiver, Direction direction, + Collection updateElements) { + if (updateElements.isEmpty()) + return; + ReteContainer affectedContainer = receiver.getContainer(); + synchronized (globalTerminationCriteria) { + long newCriterion = affectedContainer.sendUpdatesToLocalAddress(receiver, direction, updateElements); + terminationCriterion(affectedContainer, newCriterion); + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may + * reside in any of the containers associated with this network. To be called from a user thread during normal + * operation, NOT during construction. + * + * @since 2.4 + */ + public void sendExternalUpdate(Address receiver, Direction direction, Tuple updateElement) { + if (threads > 0) { + try { + updateLock.lock(); + sendUpdate(receiver, direction, updateElement); + } finally { + updateLock.unlock(); + } + } else { + sendUpdateSingleThreaded(receiver, direction, updateElement); + // getHeadContainer(). + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may + * reside in any of the containers associated with this network. To be called from a user thread during + * construction. + * + * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may + * span through network calls, that's why it's not enforced here ) + * + * @return the value of the target container's clock at the time when the message was accepted into its message + * queue + * @since 2.4 + */ + public void sendConstructionUpdate(Address receiver, Direction direction, Tuple updateElement) { + // structuralChangeLock.lock(); + if (threads > 0) + sendUpdate(receiver, direction, updateElement); + else + receiver.getContainer().sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); + // structuralChangeLock.unlock(); + } + + /** + * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial + * matching. The node may reside in any of the containers associated with this network. To be called from a user + * thread during construction. + * + * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may + * span through network calls, that's why it's not enforced here ) + * + * @since 2.4 + */ + public void sendConstructionUpdates(Address receiver, Direction direction, + Collection updateElements) { + // structuralChangeLock.lock(); + if (threads > 0) + sendUpdates(receiver, direction, updateElements); + else + receiver.getContainer().sendUpdatesToLocalAddressSingleThreaded(receiver, direction, updateElements); + // structuralChangeLock.unlock(); + } + + /** + * Establishes connection between a supplier and a receiver node, regardless which container they are in. Not to be + * called remotely, because this method enforces the structural lock. + * + * @param supplier + * @param receiver + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteNodes(Address supplier, Address receiver, + boolean synchronise) { + try { + if (threads > 0) + structuralChangeLock.lock(); + receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise); + } finally { + if (threads > 0) + structuralChangeLock.unlock(); + } + } + + /** + * Severs connection between a supplier and a receiver node, regardless which container they are in. Not to be + * called remotely, because this method enforces the structural lock. + * + * @param supplier + * @param receiver + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteNodes(Address supplier, Address receiver, + boolean desynchronise) { + try { + if (threads > 0) + structuralChangeLock.lock(); + receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise); + } finally { + if (threads > 0) + structuralChangeLock.unlock(); + } + } + + /** + * Containers use this method to report whenever they run out of messages in their queue. + * + * To be called from the thread of the reporting container. + * + * @pre threads > 0. + * @param reportingContainer + * the container reporting the emptiness of its message queue. + * @param clock + * the value of the container's clock when reporting. + * @param localTerminationCriteria + * the latest clock values this container has received from other containers since the last time it + * reported termination. + */ + void reportLocalUpdateTermination(ReteContainer reportingContainer, long clock, + Map localTerminationCriteria) { + synchronized (globalTerminationCriteria) { + for (Entry criterion : localTerminationCriteria.entrySet()) { + terminationCriterion(criterion.getKey(), criterion.getValue()); + } + + reportedClocks.put(reportingContainer, clock); + Long criterion = globalTerminationCriteria.get(reportingContainer); + if (criterion != null && criterion < clock) + globalTerminationCriteria.remove(reportingContainer); + + if (globalTerminationCriteria.isEmpty()) + globalTerminationCriteria.notifyAll(); + } + } + + /** + * @pre threads > 0 + */ + private void terminationCriterion(ReteContainer affectedContainer, long newCriterion) { + synchronized (globalTerminationCriteria) { + Long oldCriterion = globalTerminationCriteria.get(affectedContainer); + Long oldClock = reportedClocks.get(affectedContainer); + long relevantClock = oldClock == null ? 0 : oldClock; + if ((relevantClock <= newCriterion) && (oldCriterion == null || oldCriterion < newCriterion)) { + globalTerminationCriteria.put(affectedContainer, newCriterion); + } + } + } + + /** + * Waits until all rete update operations are settled in all containers. Returns immediately, if no updates are + * pending. + * + * To be called from any user thread. + */ + public void waitForReteTermination() { + if (threads > 0) { + synchronized (globalTerminationCriteria) { + while (!globalTerminationCriteria.isEmpty()) { + try { + globalTerminationCriteria.wait(); + } catch (InterruptedException e) { + + } + } + } + } else + headContainer.deliverMessagesSingleThreaded(); + } + + /** + * Waits to execute action until all rete update operations are settled in all containers. Runs action and returns + * immediately, if no updates are pending. The given action is guaranteed to be run when the terminated state still + * persists. + * + * @param action + * the action to be run when reaching the steady-state. + * + * To be called from any user thread. + */ + public void waitForReteTermination(Runnable action) { + if (threads > 0) { + synchronized (globalTerminationCriteria) { + while (!globalTerminationCriteria.isEmpty()) { + try { + globalTerminationCriteria.wait(); + } catch (InterruptedException e) { + + } + } + action.run(); + } + } else { + headContainer.deliverMessagesSingleThreaded(); + action.run(); + } + + } + + /** + * @return an unmodifiable set of known recipe traces + */ + public Set getRecipeTraces() { + return Collections.unmodifiableSet(recipeTraces); + } + + /** + * @return an unmodifiable list of containers + */ + public List getContainers() { + return Collections.unmodifiableList(containers); + } + + public Lock getStructuralChangeLock() { + return structuralChangeLock; + } + + public NodeFactory getNodeFactory() { + return nodeFactory; + } + + public InputConnector getInputConnector() { + return inputConnector; + } + + public ReteEngine getEngine() { + return engine; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; + +/** + * {@link Node}s implementing this interface are sensitive to changes in the dependency graph maintained by the + * {@link CommunicationTracker}. The {@link CommunicationTracker} notifies these nodes whenever the SCC of this node is + * affected by changes to the dependency graph. Depending on whether this node is contained in a recursive group or not, + * it may behave differently, and the {@link NetworkStructureChangeSensitiveNode#networkStructureChanged()} method can + * be used to perform changes in behavior. + * + * @author Tamas Szabo + * @since 2.3 + */ +public interface NetworkStructureChangeSensitiveNode extends Node { + + /** + * At the time of the invocation, the dependency graph has already been updated. + */ + public void networkStructureChanged(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Set; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * A node of a rete network, should be uniquely identified by network and nodeId. NodeId can be requested by registering + * at the Network on construction. + * + * @author Gabor Bergmann + */ +public interface Node { + /** + * @return the network this node belongs to. + */ + ReteContainer getContainer(); + + /** + * @return the identifier unique to this node within the network. + */ + long getNodeId(); + + /** + * Assigns a descriptive tag to the node + */ + void setTag(Object tag); + + /** + * @return the tag of the node + */ + Object getTag(); + + /** + * @return unmodifiable view of the list of traceability infos assigned to this node + */ + Set getTraceInfos(); + + /** + * assigns new traceability info to this node + */ + void assignTraceInfo(TraceInfo traceInfo); + /** + * accepts traceability info propagated to this node + */ + void acceptPropagatedTraceInfo(TraceInfo traceInfo); + + default CommunicationTracker getCommunicationTracker() { + return getContainer().getCommunicationTracker(); + } + +} 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..a40a8b7f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java @@ -0,0 +1,375 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.EMap; +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.StronglyConnectedComponentAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.WeaklyConnectedComponentAlgorithm; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.rete.aggregation.ColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.CountNode; +import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlySequentialTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputStatelessFilterNode; +import tools.refinery.viatra.runtime.rete.eval.EvaluatorCore; +import tools.refinery.viatra.runtime.rete.eval.MemorylessEvaluatorNode; +import tools.refinery.viatra.runtime.rete.eval.OutputCachingEvaluatorNode; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.ExistenceNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.JoinNode; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.misc.ConstantNode; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.single.*; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Factory for instantiating Rete nodes. The created nodes are not connected to the network yet. + * + * @author Bergmann Gabor + * + */ +class NodeFactory { + Logger logger; + + public NodeFactory(Logger logger) { + super(); + this.logger = logger; + } + + /** + * PRE: parent node must already be created + */ + public Indexer createIndexer(ReteContainer reteContainer, IndexerRecipe recipe, Supplier parentNode, + TraceInfo... traces) { + + if (recipe instanceof ProjectionIndexerRecipe) { + return parentNode.constructIndex(toMask(recipe.getMask()), traces); + // already traced + } else if (recipe instanceof AggregatorIndexerRecipe) { + int indexOfAggregateResult = recipe.getParent().getArity(); + int resultPosition = recipe.getMask().getSourceIndices().lastIndexOf(indexOfAggregateResult); + + IAggregatorNode aggregatorNode = (IAggregatorNode) parentNode; + final Indexer result = (resultPosition == -1) ? aggregatorNode.getAggregatorOuterIndexer() + : aggregatorNode.getAggregatorOuterIdentityIndexer(resultPosition); + + for (TraceInfo traceInfo : traces) + result.assignTraceInfo(traceInfo); + return result; + } else + throw new IllegalArgumentException("Unkown Indexer recipe: " + recipe); + } + + /** + * PRE: recipe is not an indexer recipe. + */ + public Supplier createNode(ReteContainer reteContainer, ReteNodeRecipe recipe, TraceInfo... traces) { + if (recipe instanceof IndexerRecipe) + throw new IllegalArgumentException("Indexers are not created by NodeFactory: " + recipe); + + Supplier result = instantiateNodeDispatch(reteContainer, recipe); + for (TraceInfo traceInfo : traces) + result.assignTraceInfo(traceInfo); + return result; + } + + private Supplier instantiateNodeDispatch(ReteContainer reteContainer, ReteNodeRecipe recipe) { + + // Parentless + + if (recipe instanceof ConstantRecipe) + return instantiateNode(reteContainer, (ConstantRecipe) recipe); + if (recipe instanceof InputRecipe) + return instantiateNode(reteContainer, (InputRecipe) recipe); + + // SingleParentNodeRecipe + + // if (recipe instanceof ProjectionIndexer) + // return instantiateNode((ProjectionIndexer)recipe); + if (recipe instanceof InputFilterRecipe) + return instantiateNode(reteContainer, (InputFilterRecipe) recipe); + if (recipe instanceof InequalityFilterRecipe) + return instantiateNode(reteContainer, (InequalityFilterRecipe) recipe); + if (recipe instanceof EqualityFilterRecipe) + return instantiateNode(reteContainer, (EqualityFilterRecipe) recipe); + if (recipe instanceof TransparentRecipe) + return instantiateNode(reteContainer, (TransparentRecipe) recipe); + if (recipe instanceof TrimmerRecipe) + return instantiateNode(reteContainer, (TrimmerRecipe) recipe); + if (recipe instanceof TransitiveClosureRecipe) + return instantiateNode(reteContainer, (TransitiveClosureRecipe) recipe); + if (recipe instanceof RepresentativeElectionRecipe) + return instantiateNode(reteContainer, (RepresentativeElectionRecipe) recipe); + if (recipe instanceof RelationEvaluationRecipe) + return instantiateNode(reteContainer, (RelationEvaluationRecipe) recipe); + if (recipe instanceof ExpressionEnforcerRecipe) + return instantiateNode(reteContainer, (ExpressionEnforcerRecipe) recipe); + if (recipe instanceof CountAggregatorRecipe) + return instantiateNode(reteContainer, (CountAggregatorRecipe) recipe); + if (recipe instanceof SingleColumnAggregatorRecipe) + return instantiateNode(reteContainer, (SingleColumnAggregatorRecipe) recipe); + if (recipe instanceof DiscriminatorDispatcherRecipe) + return instantiateNode(reteContainer, (DiscriminatorDispatcherRecipe) recipe); + if (recipe instanceof DiscriminatorBucketRecipe) + return instantiateNode(reteContainer, (DiscriminatorBucketRecipe) recipe); + + // MultiParentNodeRecipe + if (recipe instanceof UniquenessEnforcerRecipe) + return instantiateNode(reteContainer, (UniquenessEnforcerRecipe) recipe); + if (recipe instanceof ProductionRecipe) + return instantiateNode(reteContainer, (ProductionRecipe) recipe); + + // BetaNodeRecipe + if (recipe instanceof JoinRecipe) + return instantiateNode(reteContainer, (JoinRecipe) recipe); + if (recipe instanceof SemiJoinRecipe) + return instantiateNode(reteContainer, (SemiJoinRecipe) recipe); + if (recipe instanceof AntiJoinRecipe) + return instantiateNode(reteContainer, (AntiJoinRecipe) recipe); + + // ... else + throw new IllegalArgumentException("Unsupported recipe type: " + recipe); + } + + // INSTANTIATION for recipe types + + private Supplier instantiateNode(ReteContainer reteContainer, InputRecipe recipe) { + return new ExternalInputEnumeratorNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, InputFilterRecipe recipe) { + return new ExternalInputStatelessFilterNode(reteContainer, toMaskOrNull(recipe.getMask())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, CountAggregatorRecipe recipe) { + return new CountNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, TransparentRecipe recipe) { + return new TransparentNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, ExpressionEnforcerRecipe recipe) { + final IExpressionEvaluator evaluator = toIExpressionEvaluator(recipe.getExpression()); + final Map posMapping = toStringIndexMap(recipe.getMappedIndices()); + final int sourceTupleWidth = recipe.getParent().getArity(); + EvaluatorCore core = null; + if (recipe instanceof CheckRecipe) { + core = new EvaluatorCore.PredicateEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth); + } else if (recipe instanceof EvalRecipe) { + final boolean isUnwinding = ((EvalRecipe) recipe).isUnwinding(); + core = new EvaluatorCore.FunctionEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth, isUnwinding); + } else { + throw new IllegalArgumentException("Unhandled expression enforcer recipe: " + recipe.getClass() + "!"); + } + if (recipe.isCacheOutput()) { + return new OutputCachingEvaluatorNode(reteContainer, core); + } else { + return new MemorylessEvaluatorNode(reteContainer, core); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Supplier instantiateNode(ReteContainer reteContainer, SingleColumnAggregatorRecipe recipe) { + final IMultisetAggregationOperator operator = recipe.getMultisetAggregationOperator(); + TupleMask coreMask = null; + if (recipe.getOptionalMonotonicityInfo() != null) { + coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + } else { + coreMask = toMask(recipe.getGroupByMask()); + } + + if (reteContainer.isTimelyEvaluation()) { + final TimelyConfiguration timelyConfiguration = reteContainer.getTimelyConfiguration(); + final AggregatorArchitecture aggregatorArchitecture = timelyConfiguration.getAggregatorArchitecture(); + final TimelineRepresentation timelineRepresentation = timelyConfiguration.getTimelineRepresentation(); + + TupleMask posetMask = null; + + if (recipe.getOptionalMonotonicityInfo() != null) { + posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + } else { + final int aggregatedColumn = recipe.getAggregableIndex(); + posetMask = TupleMask.selectSingle(aggregatedColumn, coreMask.sourceWidth); + } + + if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY + && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) { + return new FirstOnlySequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY + && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) { + return new FirstOnlyParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL + && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) { + return new FaithfulSequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL + && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) { + return new FaithfulParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else { + throw new IllegalArgumentException("Unsupported timely configuration!"); + } + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + final TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + final IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new ColumnAggregatorNode(reteContainer, operator, recipe.isDeleteRederiveEvaluation(), coreMask, + posetMask, posetComparator); + } else { + final int aggregatedColumn = recipe.getAggregableIndex(); + return new ColumnAggregatorNode(reteContainer, operator, coreMask, aggregatedColumn); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, TransitiveClosureRecipe recipe) { + return new TransitiveClosureNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, RepresentativeElectionRecipe recipe) { + RepresentativeElectionAlgorithm.Factory algorithmFactory = switch (recipe.getConnectivity()) { + case STRONG -> StronglyConnectedComponentAlgorithm::new; + case WEAK -> WeaklyConnectedComponentAlgorithm::new; + }; + return new RepresentativeElectionNode(reteContainer, algorithmFactory); + } + + private Supplier instantiateNode(ReteContainer reteContainer, RelationEvaluationRecipe recipe) { + return new RelationEvaluatorNode(reteContainer, toIRelationEvaluator(recipe.getEvaluator())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, ProductionRecipe recipe) { + if (reteContainer.isTimelyEvaluation()) { + return new TimelyProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices())); + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()), + recipe.isDeleteRederiveEvaluation(), coreMask, posetMask, posetComparator); + } else { + return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()), + recipe.isDeleteRederiveEvaluation()); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, UniquenessEnforcerRecipe recipe) { + if (reteContainer.isTimelyEvaluation()) { + return new TimelyUniquenessEnforcerNode(reteContainer, recipe.getArity()); + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation(), + coreMask, posetMask, posetComparator); + } else { + return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation()); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, ConstantRecipe recipe) { + final List constantValues = recipe.getConstantValues(); + final Object[] constantArray = constantValues.toArray(new Object[constantValues.size()]); + return new ConstantNode(reteContainer, Tuples.flatTupleOf(constantArray)); + } + + private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorBucketRecipe recipe) { + return new DiscriminatorBucketNode(reteContainer, recipe.getBucketKey()); + } + + private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorDispatcherRecipe recipe) { + return new DiscriminatorDispatcherNode(reteContainer, recipe.getDiscriminationColumnIndex()); + } + + private Supplier instantiateNode(ReteContainer reteContainer, TrimmerRecipe recipe) { + return new TrimmerNode(reteContainer, toMask(recipe.getMask())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, InequalityFilterRecipe recipe) { + Tunnel result = new InequalityFilterNode(reteContainer, recipe.getSubject(), + TupleMask.fromSelectedIndices(recipe.getParent().getArity(), recipe.getInequals())); + return result; + } + + private Supplier instantiateNode(ReteContainer reteContainer, EqualityFilterRecipe recipe) { + final int[] equalIndices = TupleMask.integersToIntArray(recipe.getIndices()); + return new EqualityFilterNode(reteContainer, equalIndices); + } + + private Supplier instantiateNode(ReteContainer reteContainer, AntiJoinRecipe recipe) { + return new ExistenceNode(reteContainer, true); + } + + private Supplier instantiateNode(ReteContainer reteContainer, SemiJoinRecipe recipe) { + return new ExistenceNode(reteContainer, false); + } + + private Supplier instantiateNode(ReteContainer reteContainer, JoinRecipe recipe) { + return new JoinNode(reteContainer, toMask(recipe.getRightParentComplementaryMask())); + } + + // HELPERS + + private IExpressionEvaluator toIExpressionEvaluator(ExpressionDefinition expressionDefinition) { + final Object evaluator = expressionDefinition.getEvaluator(); + if (evaluator instanceof IExpressionEvaluator) { + return (IExpressionEvaluator) evaluator; + } + throw new IllegalArgumentException("No runtime support for expression evaluator: " + evaluator); + } + + private IRelationEvaluator toIRelationEvaluator(ExpressionDefinition expressionDefinition) { + final Object evaluator = expressionDefinition.getEvaluator(); + if (evaluator instanceof IRelationEvaluator) { + return (IRelationEvaluator) evaluator; + } + throw new IllegalArgumentException("No runtime support for relation evaluator: " + evaluator); + } + + private Map toStringIndexMap(final EMap mappedIndices) { + final HashMap result = new HashMap(); + for (java.util.Map.Entry entry : mappedIndices) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } + + /** Mask can be null */ + private TupleMask toMaskOrNull(Mask mask) { + if (mask == null) + return null; + else + return toMask(mask); + } + + /** Mask is non-null. */ + private TupleMask toMask(Mask mask) { + return TupleMask.fromSelectedIndices(mask.getSourceArity(), mask.getSourceIndices()); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.OnetimeIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipeRecognizer; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.remote.RemoteReceiver; +import tools.refinery.viatra.runtime.rete.remote.RemoteSupplier; +import tools.refinery.viatra.runtime.rete.traceability.ActiveNodeConflictTrace; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.traceability.UserRequestTrace; +import tools.refinery.viatra.runtime.rete.util.Options; + +import java.util.Map; +import java.util.Set; + +/** + * Stores the internal parts of a rete network. Nodes are stored according to type and parameters. + * + * @author Gabor Bergmann + */ +public class NodeProvisioner { + + // boolean activeStorage = true; + + ReteContainer reteContainer; + NodeFactory nodeFactory; + ConnectionFactory connectionFactory; + InputConnector inputConnector; + IQueryRuntimeContext runtimeContext; + + // TODO as recipe? + Map remoteReceivers = CollectionsFactory.createMap(); + Map, RemoteSupplier> remoteSuppliers = CollectionsFactory.createMap(); + + private RecipeRecognizer recognizer; + + /** + * PRE: NodeFactory, ConnectionFactory must exist + * + * @param reteContainer + * the ReteNet whose interior is to be mapped. + */ + public NodeProvisioner(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + this.nodeFactory = reteContainer.getNodeFactory(); + this.connectionFactory = reteContainer.getConnectionFactory(); + this.inputConnector = reteContainer.getInputConnectionFactory(); + runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext(); + recognizer = new RecipeRecognizer(runtimeContext); + } + + public synchronized Address getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) { + ReteNodeRecipe recipe = recipeTrace.getRecipe(); + Address result = getNodesByRecipe().get(recipe); + if (result != null) { + // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace + if (getRecipeTraces().add(recipeTrace)) + result.getNodeCache().assignTraceInfo(recipeTrace); + } else { + // No node for this recipe object - but equivalent recipes still + // reusable + ReteNodeRecipe canonicalRecipe = recognizer.canonicalizeRecipe(recipe); + if (canonicalRecipe != recipe) { + // FOUND EQUIVALENT RECIPE + result = getNodesByRecipe().get(canonicalRecipe); + if (result != null) { + // NODE ALREADY CONSTRUCTED FOR EQUIVALENT RECIPE + recipeTrace.shadowWithEquivalentRecipe(canonicalRecipe); + getNodesByRecipe().put(recipe, result); + if (getRecipeTraces().add(recipeTrace)) + result.getNodeCache().assignTraceInfo(recipeTrace); + // Bug 491922: ensure that recipe shadowing propagates to + // parent traces + // note that if equivalentRecipes() becomes more + // sophisticated + // and considers recipes with different parents, this might + // have to be changed + ensureParents(recipeTrace); + } else { + // CONSTRUCTION IN PROGRESS FOR EQUIVALENT RECIPE + if (recipe instanceof IndexerRecipe) { + // this is allowed for indexers; + // go on with the construction, as the same indexer node + // will be obtained anyways + } else { + throw new IllegalStateException( + "This should not happen: " + "non-indexer nodes are are supposed to be constructed " + + "as soon as they are designated as canonical recipes"); + } + } + } + if (result == null) { + // MUST INSTANTIATE NEW NODE FOR RECIPE + final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe); + result = reteContainer.makeAddress(freshNode); + } + } + return result; + } + + private Set getRecipeTraces() { + return reteContainer.network.recipeTraces; + } + + private Node instantiateNodeForRecipe(RecipeTraceInfo recipeTrace, final ReteNodeRecipe recipe) { + this.getRecipeTraces().add(recipeTrace); + if (recipe instanceof IndexerRecipe) { + + // INSTANTIATE AND HOOK UP + // (cannot delay hooking up, because parent determines indexer + // implementation) + ensureParents(recipeTrace); + final ReteNodeRecipe parentRecipe = recipeTrace.getParentRecipeTraces().iterator().next().getRecipe(); + final Indexer result = nodeFactory.createIndexer(reteContainer, (IndexerRecipe) recipe, + asSupplier( + (Address) reteContainer.network.getExistingNodeByRecipe(parentRecipe)), + recipeTrace); + + // REMEMBER + if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { + getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); + } + + return result; + } else { + + // INSTANTIATE + Node result = nodeFactory.createNode(reteContainer, recipe, recipeTrace); + + // REMEMBER + if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) { + getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); + } + + // HOOK UP + // (recursion-tolerant due to this delayed order of initialization) + if (recipe instanceof InputRecipe) { + inputConnector.connectInput((InputRecipe) recipe, result); + } else { + if (recipe instanceof InputFilterRecipe) + inputConnector.connectInputFilter((InputFilterRecipe) recipe, result); + ensureParents(recipeTrace); + connectionFactory.connectToParents(recipeTrace, result); + } + return result; + } + } + + private Map> getNodesByRecipe() { + return reteContainer.network.nodesByRecipe; + } + + private void ensureParents(RecipeTraceInfo recipeTrace) { + for (RecipeTraceInfo parentTrace : recipeTrace.getParentRecipeTraces()) { + getOrCreateNodeByRecipe(parentTrace); + } + } + + //// Remoting - TODO eliminate? + + synchronized RemoteReceiver accessRemoteReceiver(Address address) { + throw new UnsupportedOperationException("Multi-container Rete not supported yet"); + // if (!reteContainer.isLocal(address)) + // return + // address.getContainer().getProvisioner().accessRemoteReceiver(address); + // Supplier localSupplier = reteContainer.resolveLocal(address); + // RemoteReceiver result = remoteReceivers.get(localSupplier); + // if (result == null) { + // result = new RemoteReceiver(reteContainer); + // reteContainer.connect(localSupplier, result); // stateless node, no + // // synch required + // + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + // remoteReceivers.put(localSupplier, result); + // } + // return result; + } + + /** + * @pre: address is NOT local + */ + synchronized RemoteSupplier accessRemoteSupplier(Address address) { + throw new UnsupportedOperationException("Multi-container Rete not supported yet"); + // RemoteSupplier result = remoteSuppliers.get(address); + // if (result == null) { + // result = new RemoteSupplier(reteContainer, + // address.getContainer().getProvisioner() + // .accessRemoteReceiver(address)); + // // network.connectAndSynchronize(supplier, result); + // + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + // remoteSuppliers.put(address, result); + // } + // return result; + } + + /** + * The powerful method for accessing any (supplier) Address as a local supplier. + */ + public Supplier asSupplier(Address address) { + if (!reteContainer.isLocal(address)) + return accessRemoteSupplier(address); + else + return reteContainer.resolveLocal(address); + } + + /** the composite key tuple is formed as (RecipeTraceInfo, TupleMask) */ + private Map projectionIndexerUserRequests = CollectionsFactory.createMap(); + + // local version + // TODO remove? + public synchronized ProjectionIndexer accessProjectionIndexer(RecipeTraceInfo productionTrace, TupleMask mask) { + Tuple tableKey = Tuples.staticArityFlatTupleOf(productionTrace, mask); + UserRequestTrace indexerTrace = projectionIndexerUserRequests.computeIfAbsent(tableKey, k -> { + final ProjectionIndexerRecipe projectionIndexerRecipe = projectionIndexerRecipe( + productionTrace, mask); + return new UserRequestTrace(projectionIndexerRecipe, productionTrace); + }); + final Address address = getOrCreateNodeByRecipe(indexerTrace); + return (ProjectionIndexer) reteContainer.resolveLocal(address); + } + + // local version + public synchronized ProjectionIndexer accessProjectionIndexerOnetime(RecipeTraceInfo supplierTrace, + TupleMask mask) { + if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + return accessProjectionIndexer(supplierTrace, mask); + + final Address supplierAddress = getOrCreateNodeByRecipe(supplierTrace); + Supplier supplier = (Supplier) reteContainer.resolveLocal(supplierAddress); + + OnetimeIndexer result = new OnetimeIndexer(reteContainer, mask); + reteContainer.getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, result, reteContainer)); + + return result; + } + + // local, read-only version + public synchronized ProjectionIndexer peekProjectionIndexer(RecipeTraceInfo supplierTrace, TupleMask mask) { + final Address address = getNodesByRecipe().get(projectionIndexerRecipe(supplierTrace, mask)); + return address == null ? null : (ProjectionIndexer) reteContainer.resolveLocal(address); + } + + private ProjectionIndexerRecipe projectionIndexerRecipe( + RecipeTraceInfo parentTrace, TupleMask mask) { + final ReteNodeRecipe parentRecipe = parentTrace.getRecipe(); + Tuple tableKey = Tuples.staticArityFlatTupleOf(parentRecipe, mask); + ProjectionIndexerRecipe projectionIndexerRecipe = resultSeedRecipes.computeIfAbsent(tableKey, k -> + RecipesHelper.projectionIndexerRecipe(parentRecipe, CompilerHelper.toRecipeMask(mask)) + ); + return projectionIndexerRecipe; + } + + /** the composite key tuple is formed as (ReteNodeRecipe, TupleMask) */ + private Map resultSeedRecipes = CollectionsFactory.createMap(); + + // public synchronized Address + // accessValueBinderFilterNode( + // Address supplierAddress, int bindingIndex, Object + // bindingValue) { + // Supplier supplier = asSupplier(supplierAddress); + // Object[] paramsArray = { supplier.getNodeId(), bindingIndex, bindingValue + // }; + // Tuple params = new FlatTuple(paramsArray); + // ValueBinderFilterNode result = valueBinderFilters.get(params); + // if (result == null) { + // result = new ValueBinderFilterNode(reteContainer, bindingIndex, + // bindingValue); + // reteContainer.connect(supplier, result); // stateless node, no synch + // // required + // + // if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) + // valueBinderFilters.put(params, result); + // } + // return reteContainer.makeAddress(result); + // } + + /** + * Returns a copy of the given indexer that is an active node by itself (created if does not exist). (Convention: + * attached with same mask to a transparent node that is attached to parent node.) Node is created if it does not + * exist yet. + * + * @return an identical but active indexer + */ + // TODO rethink traceability + RecipeTraceInfo accessActiveIndexer(RecipeTraceInfo inactiveIndexerRecipeTrace) { + final RecipeTraceInfo parentRecipeTrace = inactiveIndexerRecipeTrace.getParentRecipeTraces().iterator().next(); + final ProjectionIndexerRecipe inactiveIndexerRecipe = (ProjectionIndexerRecipe) inactiveIndexerRecipeTrace + .getRecipe(); + + final TransparentRecipe transparentRecipe = RecipesFactory.eINSTANCE.createTransparentRecipe(); + transparentRecipe.setParent(parentRecipeTrace.getRecipe()); + final ActiveNodeConflictTrace transparentRecipeTrace = new ActiveNodeConflictTrace(transparentRecipe, + parentRecipeTrace, inactiveIndexerRecipeTrace); + + final ProjectionIndexerRecipe activeIndexerRecipe = RecipesFactory.eINSTANCE + .createProjectionIndexerRecipe(); + activeIndexerRecipe.setParent(transparentRecipe); + activeIndexerRecipe.setMask(inactiveIndexerRecipe.getMask()); + final ActiveNodeConflictTrace activeIndexerRecipeTrace = new ActiveNodeConflictTrace(activeIndexerRecipe, + transparentRecipeTrace, inactiveIndexerRecipeTrace); + + return activeIndexerRecipeTrace; + } + + // /** + // * @param parent + // * @return + // */ + // private TransparentNode accessTransparentNodeInternal(Supplier parent) { + // nodeFactory. + // return null; + // } + + // public synchronized void registerSpecializedProjectionIndexer(Node node, + // ProjectionIndexer indexer) { + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { + // Object[] paramsArray = { node.getNodeId(), indexer.getMask() }; + // Tuple params = new FlatTuple(paramsArray); + // projectionIndexers.put(params, indexer); + // } + // } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public interface PosetAwareReceiver extends Receiver { + + public TupleMask getCoreMask(); + + public TupleMask getPosetMask(); + + public IPosetComparator getPosetComparator(); + + /** + * Updates the receiver with a newly found or lost partial matching also providing information + * whether the update is a monotone change or not. + * + * @param direction the direction of the update + * @param update the update tuple + * @param monotone true if the update is monotone, false otherwise + * @since 2.4 + */ + public void updateWithPosetInfo(Direction direction, Tuple update, boolean monotone); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Interface intended for nodes containing complete matches. + * + * @author Gabor Bergmann + */ +public interface ProductionNode extends Tunnel, Iterable { + + /** + * @return the position mapping of this particular pattern that maps members of the tuple type to their positions + */ + Map getPosMapping(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * ALL METHODS: FOR INTERNAL USE ONLY; ONLY INVOKE FROM {@link ReteContainer} + * + * @author Gabor Bergmann + * @noimplement This interface is not intended to be implemented by external clients. + */ +public interface Receiver extends Node { + + /** + * Updates the receiver with a newly found or lost partial matching. + * + * @since 2.4 + */ + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp); + + /** + * Updates the receiver in batch style with a collection of updates. The input collection consists of pairs in the + * form (t, c) where t is an update tuple and c is the count. The count can also be negative, and it specifies how + * many times the tuple t gets deleted or inserted. The default implementation of this method simply calls + * {@link #update(Direction, Tuple, Timestamp)} individually for all updates. + * + * @since 2.8 + */ + public default void batchUpdate(final Collection> updates, final Timestamp timestamp) { + for (final Entry entry : updates) { + int count = entry.getValue(); + + Direction direction; + if (count < 0) { + direction = Direction.DELETE; + count = -count; + } else { + direction = Direction.INSERT; + } + + for (int i = 0; i < count; i++) { + update(direction, entry.getKey(), timestamp); + } + } + } + + /** + * Returns the {@link Mailbox} of this receiver. + * + * @return the mailbox + * @since 2.0 + */ + public Mailbox getMailbox(); + + /** + * appends a parent that will continuously send insert and revoke updates to this supplier + */ + void appendParent(final Supplier supplier); + + /** + * removes a parent + */ + void removeParent(final Supplier supplier); + + /** + * access active parent + */ + Collection getParents(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +/** + * A rederivable node can potentially re-derive tuples after the Rete network has finished the delivery of messages. + * + * @author Tamas Szabo + * @since 1.6 + */ +public interface RederivableNode extends Node, IGroupable { + + /** + * The method is called by the {@link ReteContainer} to re-derive tuples after the normal messages have been + * delivered and consumed. The re-derivation process may trigger the creation and delivery of further messages + * and further re-derivation rounds. + */ + public void rederiveOne(); + + /** + * Returns true if this node actually runs in DRed mode (not necessarily). + * + * @return true if the node is operating in DRed mode + * @since 2.0 + */ + public boolean isInDRedMode(); + +} 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 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +import java.util.Collection; + +public interface ReinitializedNode { + void reinitializeWith(Collection tuples); +} 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..16e290fd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java @@ -0,0 +1,729 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedCommand; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedDisconnectCommand; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * @author Gabor Bergmann + * + * Mutexes: externalMessageLock - enlisting messages into and retrieving from the external message queue + * @since 2.2 + */ +public final class ReteContainer { + + protected Thread consumerThread = null; + protected boolean killed = false; + + protected Network network; + + protected LinkedList clearables; + protected Map nodesById; + protected long nextId = 0; + + protected ConnectionFactory connectionFactory; + protected NodeProvisioner nodeProvisioner; + + protected Deque internalMessageQueue = new ArrayDeque(); + protected/* volatile */Deque externalMessageQueue = new ArrayDeque(); + protected Object externalMessageLock = new Object(); + protected Long clock = 1L; // even: steady state, odd: active queue; access + // ONLY with messageQueue locked! + protected Map terminationCriteria = null; + protected final Logger logger; + protected final CommunicationTracker tracker; + + protected final IQueryBackendContext backendContext; + + protected Set delayedCommandQueue; + protected Set delayedCommandBuffer; + protected boolean executingDelayedCommands; + + protected final TimelyConfiguration timelyConfiguration; + + /** + * @param threaded + * false if operating in a single-threaded environment + */ + public ReteContainer(Network network, boolean threaded) { + super(); + this.network = network; + this.backendContext = network.getEngine().getBackendContext(); + this.timelyConfiguration = network.getEngine().getTimelyConfiguration(); + + this.delayedCommandQueue = new LinkedHashSet(); + this.delayedCommandBuffer = new LinkedHashSet(); + this.executingDelayedCommands = false; + + if (this.isTimelyEvaluation()) { + this.tracker = new TimelyCommunicationTracker(this.getTimelyConfiguration()); + } else { + this.tracker = new TimelessCommunicationTracker(); + } + + this.nodesById = CollectionsFactory.createMap(); + this.clearables = new LinkedList(); + this.logger = network.getEngine().getLogger(); + + this.connectionFactory = new ConnectionFactory(this); + this.nodeProvisioner = new NodeProvisioner(this); + + if (threaded) { + this.terminationCriteria = CollectionsFactory.createMap(); + this.consumerThread = new Thread("Rete thread of " + ReteContainer.super.toString()) { + @Override + public void run() { + messageConsumptionCycle(); + } + }; + this.consumerThread.start(); + } + } + + /** + * @since 2.4 + */ + public boolean isTimelyEvaluation() { + return this.timelyConfiguration != null; + } + + /** + * @since 2.4 + */ + public TimelyConfiguration getTimelyConfiguration() { + return this.timelyConfiguration; + } + + /** + * @since 1.6 + * @return the communication graph of the nodes, incl. message scheduling + */ + public CommunicationTracker getCommunicationTracker() { + return tracker; + } + + /** + * Stops this container. To be called by Network.kill() + */ + public void kill() { + killed = true; + if (consumerThread != null) + consumerThread.interrupt(); + } + + /** + * Establishes connection between a supplier and a receiver node, regardless which container they are in. Assumption + * is that this container is the home of the receiver, but it is not strictly necessary. + * + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteNodes(Address supplier, Address receiver, + boolean synchronise) { + if (!isLocal(receiver)) + receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise); + else { + Receiver child = resolveLocal(receiver); + connectRemoteSupplier(supplier, child, synchronise); + } + } + + /** + * Severs connection between a supplier and a receiver node, regardless which container they are in. Assumption is + * that this container is the home of the receiver, but it is not strictly necessary. + * + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteNodes(Address supplier, Address receiver, + boolean desynchronise) { + if (!isLocal(receiver)) + receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise); + else { + Receiver child = resolveLocal(receiver); + disconnectRemoteSupplier(supplier, child, desynchronise); + } + } + + /** + * Establishes connection between a remote supplier and a local receiver node. + * + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteSupplier(Address supplier, Receiver receiver, boolean synchronise) { + Supplier parent = nodeProvisioner.asSupplier(supplier); + if (synchronise) + connectAndSynchronize(parent, receiver); + else + connect(parent, receiver); + } + + /** + * Severs connection between a remote supplier and a local receiver node. + * + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteSupplier(Address supplier, Receiver receiver, + boolean desynchronise) { + Supplier parent = nodeProvisioner.asSupplier(supplier); + if (desynchronise) + disconnectAndDesynchronize(parent, receiver); + else + disconnect(parent, receiver); + } + + /** + * Connects a receiver to a supplier + */ + public void connect(Supplier supplier, Receiver receiver) { + supplier.appendChild(receiver); + receiver.appendParent(supplier); + tracker.registerDependency(supplier, receiver); + } + + /** + * Disconnects a receiver from a supplier + */ + public void disconnect(Supplier supplier, Receiver receiver) { + supplier.removeChild(receiver); + receiver.removeParent(supplier); + tracker.unregisterDependency(supplier, receiver); + } + + /** + * @since 2.3 + */ + public boolean isExecutingDelayedCommands() { + return this.executingDelayedCommands; + } + + /** + * @since 2.3 + */ + public Set getDelayedCommandQueue() { + if (this.executingDelayedCommands) { + return this.delayedCommandBuffer; + } else { + return this.delayedCommandQueue; + } + } + + /** + * Connects a receiver to a remote supplier, and synchronizes it to the current contents of the supplier + */ + public void connectAndSynchronize(Supplier supplier, Receiver receiver) { + supplier.appendChild(receiver); + receiver.appendParent(supplier); + tracker.registerDependency(supplier, receiver); + getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, receiver, this)); + } + + /** + * Disconnects a receiver from a supplier + */ + public void disconnectAndDesynchronize(Supplier supplier, Receiver receiver) { + final boolean wasInSameSCC = this.isTimelyEvaluation() && this.tracker.areInSameGroup(supplier, receiver); + supplier.removeChild(receiver); + receiver.removeParent(supplier); + tracker.unregisterDependency(supplier, receiver); + getDelayedCommandQueue().add(new DelayedDisconnectCommand(supplier, receiver, this, wasInSameSCC)); + } + + /** + * @since 2.3 + */ + public void executeDelayedCommands() { + if (!this.delayedCommandQueue.isEmpty()) { + flushUpdates(); + this.executingDelayedCommands = true; + for (final DelayedCommand command : this.delayedCommandQueue) { + command.run(); + } + this.delayedCommandQueue = this.delayedCommandBuffer; + this.delayedCommandBuffer = new LinkedHashSet(); + flushUpdates(); + this.executingDelayedCommands = false; + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is + * indicated by the Address. Designed to be called by the Network, DO NOT use in any other way. @pre: + * address.container == this, e.g. address MUST be local + * + * @return the value of the container's clock at the time when the message was accepted into the local message queue + */ + long sendUpdateToLocalAddress(Address address, Direction direction, Tuple updateElement) { + long timestamp; + Receiver receiver = resolveLocal(address); + UpdateMessage message = new UpdateMessage(receiver, direction, updateElement); + synchronized (externalMessageLock) { + externalMessageQueue.add(message); + timestamp = clock; + externalMessageLock.notifyAll(); + } + + return timestamp; + + } + + /** + * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial + * matching. The receiver is indicated by the Address. Designed to be called by the Network, DO NOT use in any other + * way. @pre: address.container == this, e.g. address MUST be local @pre: updateElements is nonempty! + * + * @return the value of the container's clock at the time when the message was accepted into the local message queue + */ + long sendUpdatesToLocalAddress(Address address, Direction direction, + Collection updateElements) { + + long timestamp; + Receiver receiver = resolveLocal(address); + // UpdateMessage message = new UpdateMessage(receiver, direction, + // updateElement); + synchronized (externalMessageLock) { + for (Tuple ps : updateElements) + externalMessageQueue.add(new UpdateMessage(receiver, direction, ps)); + // messageQueue.add(new UpdateMessage(resolveLocal(address), + // direction, updateElement)); + // this.sendUpdateInternal(resolveLocal(address), direction, + // updateElement); + timestamp = clock; + externalMessageLock.notifyAll(); + } + + return timestamp; + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is + * indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT use in any + * other way. + */ + void sendUpdateToLocalAddressSingleThreaded(Address address, Direction direction, + Tuple updateElement) { + Receiver receiver = resolveLocal(address); + UpdateMessage message = new UpdateMessage(receiver, direction, updateElement); + internalMessageQueue.add(message); + } + + /** + * Sends multiple update messages to the receiver node, indicating a newly found or lost partial matching. The + * receiver is indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT + * use in any other way. + * + * @pre: address.container == this, e.g. address MUST be local + */ + void sendUpdatesToLocalAddressSingleThreaded(Address address, Direction direction, + Collection updateElements) { + Receiver receiver = resolveLocal(address); + for (Tuple ps : updateElements) + internalMessageQueue.add(new UpdateMessage(receiver, direction, ps)); + } + + /** + * Sends an update message to a node in a different container. The receiver is indicated by the Address. Designed to + * be called by RemoteReceivers, DO NOT use in any other way. + * + * @since 2.4 + */ + public void sendUpdateToRemoteAddress(Address address, Direction direction, + Tuple updateElement) { + ReteContainer otherContainer = address.getContainer(); + long otherClock = otherContainer.sendUpdateToLocalAddress(address, direction, updateElement); + // Long criterion = terminationCriteria.get(otherContainer); + // if (criterion==null || otherClock > criterion) + terminationCriteria.put(otherContainer, otherClock); + } + + /** + * Finalises all update sequences and returns. To be called from user threads (e.g. network construction). + */ + public void flushUpdates() { + network.waitForReteTermination(); + // synchronized (messageQueue) + // { + // while (!messageQueue.isEmpty()) + // { + // try { + // UpdateMessage message = messageQueue.take(); + // message.receiver.update(message.direction, message.updateElement); + // } catch (InterruptedException e) {} + // } + // } + } + + /** + * Retrieves a safe copy of the contents of a supplier. + * + *

Note that there may be multiple copies of a Tuple in case of a {@link TrimmerNode}, so the result is not always a set. + * + * @param flush if true, a flush is performed before pulling the contents + * @since 2.3 + */ + public Collection pullContents(final Supplier supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Collection collector = new ArrayList(); + supplier.pullInto(collector, flush); + return collector; + } + + /** + * @since 2.4 + */ + public Map> pullContentsWithTimeline(final Supplier supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Map> collector = CollectionsFactory.createMap(); + supplier.pullIntoWithTimeline(collector, flush); + return collector; + } + + /** + * Retrieves the contents of a SingleInputNode's parentage. + * + * @since 2.3 + */ + public Collection pullPropagatedContents(final SingleInputNode supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Collection collector = new LinkedList(); + supplier.propagatePullInto(collector, flush); + return collector; + } + + /** + * Retrieves the timestamp-aware contents of a SingleInputNode's parentage. + * + * @since 2.3 + */ + public Map> pullPropagatedContentsWithTimestamp(final SingleInputNode supplier, + final boolean flush) { + if (flush) { + flushUpdates(); + } + final Map> collector = CollectionsFactory.createMap(); + supplier.propagatePullIntoWithTimestamp(collector, flush); + return collector; + } + + /** + * Retrieves the contents of a supplier for a remote caller. Assumption is that this container is the home of the + * supplier, but it is not strictly necessary. + * + * @param supplier + * the address of the supplier to be pulled. + * @since 2.3 + */ + public Collection remotePull(Address supplier, boolean flush) { + if (!isLocal(supplier)) + return supplier.getContainer().remotePull(supplier, flush); + return pullContents(resolveLocal(supplier), flush); + } + + /** + * Proxies for the getPosMapping() of Production nodes. Retrieves the posmapping of a remote or local Production to + * a remote or local caller. + */ + public Map remotePosMapping(Address production) { + if (!isLocal(production)) + return production.getContainer().remotePosMapping(production); + return resolveLocal(production).getPosMapping(); + } + + /** + * Continually consumes update messages. Should be run on a dedicated thread. + */ + void messageConsumptionCycle() { + while (!killed) // deliver messages on and on and on.... + { + long incrementedClock = 0; + UpdateMessage message = null; + + if (!internalMessageQueue.isEmpty()) // take internal messages first + message = internalMessageQueue.removeFirst(); + else + // no internal message, take an incoming message + synchronized (externalMessageLock) { // no sleeping allowed, + // because external + // queue is locked for + // precise clocking of + // termination point! + if (!externalMessageQueue.isEmpty()) { // if external queue + // is non-empty, + // retrieve the next + // message instantly + message = takeExternalMessage(); + } else { // if external queue is found empty (and this is + // the first time in a row) + incrementedClock = ++clock; // local termination point + // synchronized(clock){incrementedClock = ++clock;} + } + } + + if (message == null) // both queues were empty + { + localUpdateTermination(incrementedClock); // report local + // termination point + while (message == null) // wait for a message while external + // queue is still empty + { + synchronized (externalMessageLock) { + while (externalMessageQueue.isEmpty()) { + try { + externalMessageLock.wait(); + } catch (InterruptedException e) { + if (killed) + return; + } + } + message = takeExternalMessage(); + } + + } + } + + // now we have a message to deliver + // NOTE: this method is not compatible with differential dataflow + message.receiver.update(message.direction, message.updateElement, Timestamp.ZERO); + } + } + + /** + * @since 1.6 + */ + public static final Function NAME_MAPPER = input -> input.toString().substring(0, + Math.min(30, input.toString().length())); + + /** + * Sends out all pending messages to their receivers. The delivery is governed by the communication tracker. + * + * @since 1.6 + */ + public void deliverMessagesSingleThreaded() { + if (!backendContext.areUpdatesDelayed()) { + if (Options.MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING) { + // known unreachable; enable for debugging only + + CommunicationGroup lastGroup = null; + Set seenInThisCycle = new HashSet<>(); + + while (!tracker.isEmpty()) { + final CommunicationGroup group = tracker.getAndRemoveFirstGroup(); + + /** + * The current group does not violate the communication schema iff (1) it was not seen before OR (2) + * the last one that was seen is exactly the same as the current one this can happen if the group + * was added back because of in-group message passing + */ + boolean okGroup = (group == lastGroup) || seenInThisCycle.add(group); + + if (!okGroup) { + logger.error( + "[INTERNAL ERROR] Violation of communication schema! The communication component with representative " + + group.getRepresentative() + " has already been processed!"); + } + + group.deliverMessages(); + + lastGroup = group; + } + + } else { + while (!tracker.isEmpty()) { + final CommunicationGroup group = tracker.getAndRemoveFirstGroup(); + group.deliverMessages(); + } + } + } + } + + private void localUpdateTermination(long incrementedClock) { + network.reportLocalUpdateTermination(this, incrementedClock, terminationCriteria); + terminationCriteria.clear(); + + // synchronized(clock){++clock;} // +1 incrementing for parity and easy + // comparison + } + + // @pre: externalMessageQueue synchronized && nonempty + private UpdateMessage takeExternalMessage() { + UpdateMessage message = externalMessageQueue.removeFirst(); + if (!externalMessageQueue.isEmpty()) { // copy the whole queue over + // for speedup + Deque temp = externalMessageQueue; + externalMessageQueue = internalMessageQueue; + internalMessageQueue = temp; + } + return message; + } + + /** + * Provides an external address for the selected node. + * + * @pre node belongs to this container. + */ + public Address makeAddress(N node) { + return new Address(node); + } + + /** + * Checks whether a certain address points to a node at this container. + */ + public boolean isLocal(Address address) { + return address.getContainer() == this; + } + + /** + * Returns an addressed node at this container. + * + * @pre: address.container == this, e.g. address MUST be local + * @throws IllegalArgumentException + * if address is non-local + */ + @SuppressWarnings("unchecked") + public N resolveLocal(Address address) { + if (this != address.getContainer()) + throw new IllegalArgumentException(String.format("Address %s non-local at container %s", address, this)); + + N cached = address.getNodeCache(); + if (cached != null) + return cached; + else { + N node = (N) nodesById.get(address.getNodeId()); + address.setNodeCache(node); + return node; + } + } + + /** + * Registers a node into the rete network (should be called by constructor). Every node MUST be registered by its + * constructor. + * + * @return the unique nodeId issued to the node. + */ + public long registerNode(Node n) { + long id = nextId++; + nodesById.put(id, n); + return id; + } + + /** + * Unregisters a node from the rete network. Do NOT call if node is still connected to other Nodes, or Adressed or + * otherwise referenced. + */ + public void unregisterNode(Node n) { + nodesById.remove(n.getNodeId()); + } + + /** + * Registers a pattern memory into the rete network. Every memory MUST be registered by its owner node. + */ + public void registerClearable(Clearable c) { + clearables.addFirst(c); + } + + /** + * Unregisters a pattern memory from the rete network. + */ + public void unregisterClearable(Clearable c) { + clearables.remove(c); + } + + /** + * Clears all memory contents in the network. Reverts to initial state. + */ + public void clearAll() { + for (Clearable c : clearables) { + c.clear(); + } + } + + public NodeFactory getNodeFactory() { + return network.getNodeFactory(); + } + + public ConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + public NodeProvisioner getProvisioner() { + return nodeProvisioner; + } + + public Network getNetwork() { + return network; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String separator = System.getProperty("line.separator"); + sb.append(super.toString() + "[[[" + separator); + java.util.List keys = new java.util.ArrayList(nodesById.keySet()); + java.util.Collections.sort(keys); + for (Long key : keys) { + sb.append(key + " -> " + nodesById.get(key) + separator); + } + sb.append("]]] of " + network); + return sb.toString(); + } + + /** + * Access all the Rete nodes inside this container. + * + * @return the collection of {@link Node} instances + */ + public Collection getAllNodes() { + return nodesById.values(); + } + + public InputConnector getInputConnectionFactory() { + return network.getInputConnector(); + } + +} 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..e7ec36dc --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.index.GenericProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Base implementation for a supplier node. + * + * @author Gabor Bergmann + * + */ +public abstract class StandardNode extends BaseNode implements Supplier, NetworkStructureChangeSensitiveNode { + protected final List children = CollectionsFactory.createObserverList(); + /** + * @since 2.2 + */ + protected final List childMailboxes = CollectionsFactory.createObserverList(); + + public StandardNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * @since 2.4 + */ + protected void propagateUpdate(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + for (final Mailbox childMailbox : childMailboxes) { + childMailbox.postMessage(direction, updateElement, timestamp); + } + } + + @Override + public void appendChild(final Receiver receiver) { + children.add(receiver); + childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox())); + } + + @Override + public void removeChild(final Receiver receiver) { + children.remove(receiver); + Mailbox mailboxToRemove = null; + for (final Mailbox mailbox : childMailboxes) { + if (mailbox.getReceiver() == receiver) { + mailboxToRemove = mailbox; + break; + } + } + assert mailboxToRemove != null; + childMailboxes.remove(mailboxToRemove); + } + + @Override + public void networkStructureChanged() { + childMailboxes.clear(); + for (final Receiver receiver : children) { + childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox())); + } + } + + @Override + public Collection getReceivers() { + return children; + } + + /** + * @since 2.2 + */ + public Collection getChildMailboxes() { + return this.childMailboxes; + } + + @Override + public Set getPulledContents(final boolean flush) { + final HashSet results = new HashSet(); + pullInto(results, flush); + return results; + } + + @Override + public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) { + final GenericProjectionIndexer indexer = new GenericProjectionIndexer(reteContainer, mask); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + reteContainer.connectAndSynchronize(this, indexer); + return indexer; + } + + /** + * @since 1.6 + */ + protected void issueError(final String message, final Exception ex) { + if (ex == null) { + this.reteContainer.getNetwork().getEngine().getLogger().error(message); + } else { + this.reteContainer.getNetwork().getEngine().getLogger().error(message, ex); + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Gabor Bergmann + * + * A supplier is an object that can propagate insert or revoke events towards receivers. + */ +public interface Supplier extends Node { + + /** + * Pulls the contents of this object in this particular moment into a target collection. + * + * @param flush if true, flushing of messages is allowed during the pull, otherwise flushing is not allowed + * @since 2.3 + */ + public void pullInto(Collection collector, boolean flush); + + /** + * @since 2.4 + */ + public void pullIntoWithTimeline(final Map> collector, final boolean flush); + + /** + * Returns the contents of this object in this particular moment. + * For memoryless nodes, this may involve a costly recomputation of contents. + * + * The result is returned as a Set, even when it has multiplicities (at the output of {@link TrimmerNode}). + * + *

Intended mainly for debug purposes; therefore flushing is performed only if explicitly requested + * During runtime, flushing may be preferred; see {@link ReteContainer#pullContents(Supplier)} + * @since 2.3 + */ + public Set getPulledContents(boolean flush); + + default public Set getPulledContents() { + return getPulledContents(true); + } + + /** + * appends a receiver that will continously receive insert and revoke updates from this supplier + */ + void appendChild(Receiver receiver); + + /** + * removes a receiver + */ + void removeChild(Receiver receiver); + + /** + * Instantiates (or reuses, depending on implementation) an index according to the given mask. + * + * Intended for internal use; clients should invoke through Library instead to enable reusing. + */ + ProjectionIndexer constructIndex(TupleMask mask, TraceInfo... traces); + + /** + * lists receivers + */ + Collection getReceivers(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +/** + * @author Gabor Bergmann + * + * A Tunnel is an interface into which elments can be instered and from which productions can be extracted. + */ +public interface Tunnel extends Supplier, Receiver { + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +class UpdateMessage { + public Receiver receiver; + public Direction direction; + public Tuple updateElement; + + public UpdateMessage(Receiver receiver, Direction direction, Tuple updateElement) { + this.receiver = receiver; + this.direction = direction; + this.updateElement = updateElement; + } + + @Override + public String toString() { + return "M." + direction + ": " + updateElement + " -> " + receiver; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group represents a set of nodes in the communication graph that form a strongly connected component. + * + * @author Tamas Szabo + * @since 1.6 + */ +public abstract class CommunicationGroup implements Comparable { + + public static final String UNSUPPORTED_MESSAGE_KIND = "Unsupported message kind "; + + /** + * Marker for the {@link CommunicationTracker} + */ + public boolean isEnqueued = false; + + protected final Node representative; + + /** + * May be changed during bumping in {@link CommunicationTracker.registerDependency} + */ + protected int identifier; + + /** + * @since 1.7 + */ + protected final CommunicationTracker tracker; + + /** + * @since 1.7 + */ + public CommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + this.tracker = tracker; + this.representative = representative; + this.identifier = identifier; + } + + public abstract void deliverMessages(); + + public Node getRepresentative() { + return representative; + } + + public abstract boolean isEmpty(); + + /** + * @since 2.0 + */ + public abstract void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind); + + /** + * @since 2.0 + */ + public abstract void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind); + + public abstract Map> getMailboxes(); + + public abstract boolean isRecursive(); + + @Override + public int hashCode() { + return this.identifier; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " " + this.identifier + " - representative: " + this.representative + + " - isEmpty: " + isEmpty(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } else if (this == obj) { + return true; + } else { + final CommunicationGroup that = (CommunicationGroup) obj; + return this.identifier == that.identifier; + } + } + + @Override + public int compareTo(final CommunicationGroup that) { + return this.identifier - that.identifier; + } + +} 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..8435a547 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java @@ -0,0 +1,467 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.base.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.ExistenceNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyIndexerListenerProxy; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; + +/** + * An instance of this class is associated with every {@link ReteContainer}. The tracker serves two purposes:
+ * (1) It allows RETE nodes to register their communication dependencies on-the-fly. These dependencies can be + * registered or unregistered when nodes are disposed of.
+ * (2) It allows RETE nodes to register their mailboxes as dirty, that is, they can tell the tracker that they have + * something to send to other nodes in the network. The tracker is then responsible for ordering these messages (more + * precisely, the mailboxes that contain the messages) for the associated {@link ReteContainer}. The ordering is + * governed by the strongly connected components in the dependency network and follows a topological sorting scheme; + * those mailboxes will be emptied first whose owner nodes do not depend on other undelivered messages. + * + * @author Tamas Szabo + * @since 1.6 + * + */ +public abstract class CommunicationTracker { + + /** + * The minimum group id assigned so far + */ + protected int minGroupId; + + /** + * The maximum group id assigned so far + */ + protected int maxGroupId; + + /** + * The dependency graph of the communications in the RETE network + */ + protected final Graph dependencyGraph; + + /** + * Incremental SCC information about the dependency graph + */ + protected final IncSCCAlg sccInformationProvider; + + /** + * Precomputed node -> communication group map + */ + protected final Map groupMap; + + /** + * Priority queue of active communication groups + */ + protected final Queue groupQueue; + + // groups should have a simple integer flag which represents its position in a priority queue + // priority queue only contains the ACTIVE groups + + public CommunicationTracker() { + this.dependencyGraph = new Graph(); + this.sccInformationProvider = new IncSCCAlg(this.dependencyGraph); + this.groupQueue = new PriorityQueue(); + this.groupMap = new HashMap(); + } + + public Graph getDependencyGraph() { + return dependencyGraph; + } + + public CommunicationGroup getGroup(final Node node) { + return this.groupMap.get(node); + } + + private void precomputeGroups() { + groupMap.clear(); + + // reconstruct group map from dependency graph + final Graph reducedGraph = sccInformationProvider.getReducedGraph(); + final List representatives = TopologicalSorting.compute(reducedGraph); + + for (int i = 0; i < representatives.size(); i++) { // groups for SCC representatives + final Node representative = representatives.get(i); + createAndStoreGroup(representative, i); + } + + minGroupId = 0; + maxGroupId = representatives.size() - 1; + + for (final Node node : dependencyGraph.getAllNodes()) { // extend group map to the rest of nodes + final Node representative = sccInformationProvider.getRepresentative(node); + final CommunicationGroup group = groupMap.get(representative); + if (representative != node) { + addToGroup(node, group); + } + } + + for (final Node node : dependencyGraph.getAllNodes()) { + // set fall-through flags of default mailboxes + precomputeFallThroughFlag(node); + // perform further tracker-specific post-processing + postProcessNode(node); + } + + // reconstruct new queue contents based on new group map + if (!groupQueue.isEmpty()) { + final Set oldActiveGroups = new HashSet(groupQueue); + groupQueue.clear(); + reconstructQueueContents(oldActiveGroups); + } + + // post process the groups + for (final CommunicationGroup group : groupMap.values()) { + postProcessGroup(group); + } + } + + /** + * This method is responsible for reconstructing the active queue contents after the network structure has changed. + * It it defined as abstract because the reconstruction logic is specific to each {@link CommunicationTracker}. + * @since 2.4 + */ + protected abstract void reconstructQueueContents(final Set oldActiveGroups); + + private void addToGroup(final Node node, final CommunicationGroup group) { + groupMap.put(node, group); + if (node instanceof Receiver) { + ((Receiver) node).getMailbox().setCurrentGroup(group); + if (node instanceof IGroupable) { + ((IGroupable) node).setCurrentGroup(group); + } + } + } + + /** + * Depends on the groups, as well as the parent nodes of the argument, so recomputation is needed if these change + */ + private void precomputeFallThroughFlag(final Node node) { + CommunicationGroup group = groupMap.get(node); + if (node instanceof Receiver) { + IGroupable mailbox = ((Receiver) node).getMailbox(); + if (mailbox instanceof FallThroughCapableMailbox) { + Set directParents = dependencyGraph.getSourceNodes(node).distinctValues(); + // decide between using quick&cheap fall-through, or allowing for update cancellation + boolean fallThrough = + // disallow fallthrough: updates at production nodes should cancel, if they can be trimmed or + // disjunctive + (!(node instanceof ProductionNode && ( // it is a production node... + // with more than one parent + directParents.size() > 0 || + // or true trimming in its sole parent + directParents.size() == 1 && trueTrimming(directParents.iterator().next())))) && + // disallow fallthrough: external updates should be stored (if updates are delayed) + (!(node instanceof ExternalInputEnumeratorNode)) && + // disallow fallthrough: RelationEvaluatorNode needs to be notified in batch-style, and the batching is done by the mailbox + // however, it is not the RelationEvaluatorNode itself that is interesting here, as that indirectly uses the BatchingReceiver + // so we need to disable fall-through for the BatchingReceiver + (!(node instanceof RelationEvaluatorNode.BatchingReceiver)); + // do additional checks + if (fallThrough) { + // recursive parent groups generate excess updates that should be cancelled after delete&rederive + // phases + // aggregator and transitive closure parent nodes also generate excess updates that should be + // cancelled + directParentLoop: for (Node directParent : directParents) { + Set parentsToCheck = new HashSet<>(); + // check the case where a direct parent is the reason for mailbox usage + parentsToCheck.add(directParent); + // check the case where an indirect parent (join slot) is the reason for mailbox usage + if (directParent instanceof DualInputNode) { + // in case of existence join (typically antijoin), a mailbox should allow + // an insertion and deletion (at the secondary slot) to cancel each other out + if (directParent instanceof ExistenceNode) { + fallThrough = false; + break directParentLoop; + } + // in beta nodes, indexer slots (or their active nodes) are considered indirect parents + DualInputNode dualInput = (DualInputNode) directParent; + IterableIndexer primarySlot = dualInput.getPrimarySlot(); + if (primarySlot != null) + parentsToCheck.add(primarySlot.getActiveNode()); + Indexer secondarySlot = dualInput.getSecondarySlot(); + if (secondarySlot != null) + parentsToCheck.add(secondarySlot.getActiveNode()); + } + for (Node parent : parentsToCheck) { + CommunicationGroup parentGroup = groupMap.get(parent); + if ( // parent is in a different, recursive group + (group != parentGroup && parentGroup.isRecursive()) || + // node and parent within the same recursive group, and... + (group == parentGroup && group.isRecursive() && ( + // parent is a transitive closure or aggregator node, or a trimmer + // allow trimmed or disjunctive tuple updates to cancel each other + (parent instanceof TransitiveClosureNode) || (parent instanceof IAggregatorNode) + || trueTrimming(parent)))) { + fallThrough = false; + break directParentLoop; + } + } + } + } + // overwrite fallthrough flag with newly computed value + ((FallThroughCapableMailbox) mailbox).setFallThrough(fallThrough); + } + } + } + + /** + * A trimmer node that actually eliminates some columns (not just reorders) + */ + private boolean trueTrimming(Node node) { + if (node instanceof TrimmerNode) { + TupleMask mask = ((TrimmerNode) node).getMask(); + return (mask.indices.length != mask.sourceWidth); + } + return false; + } + + public void activateUnenqueued(final CommunicationGroup group) { + groupQueue.add(group); + group.isEnqueued = true; + } + + public void deactivate(final CommunicationGroup group) { + groupQueue.remove(group); + group.isEnqueued = false; + } + + public CommunicationGroup getAndRemoveFirstGroup() { + final CommunicationGroup group = groupQueue.poll(); + group.isEnqueued = false; + return group; + } + + public boolean isEmpty() { + return groupQueue.isEmpty(); + } + + protected abstract CommunicationGroup createGroup(final Node representative, final int index); + + protected CommunicationGroup createAndStoreGroup(final Node representative, final int index) { + final CommunicationGroup group = createGroup(representative, index); + addToGroup(representative, group); + return group; + } + + /** + * Registers the dependency that the target {@link Node} depends on the source {@link Node}. In other words, source + * may send messages to target in the RETE network. If the dependency edge is already present, this method call is a + * noop. + * + * @param source + * the source node + * @param target + * the target node + */ + public void registerDependency(final Node source, final Node target) { + // nodes can be immediately inserted, if they already exist in the graph, this is a noop + dependencyGraph.insertNode(source); + dependencyGraph.insertNode(target); + + if (!this.dependencyGraph.getTargetNodes(source).containsNonZero(target)) { + + // query all these information before the actual edge insertion + // because SCCs may be unified during the process + final Node sourceRepresentative = sccInformationProvider.getRepresentative(source); + final Node targetRepresentative = sccInformationProvider.getRepresentative(target); + final boolean targetHadOutgoingEdges = sccInformationProvider.hasOutgoingEdges(targetRepresentative); + + // insert the edge + dependencyGraph.insertEdge(source, target); + + // create groups if they do not yet exist + CommunicationGroup sourceGroup = groupMap.get(sourceRepresentative); + if (sourceGroup == null) { + // create on-demand with the next smaller group id + sourceGroup = createAndStoreGroup(sourceRepresentative, --minGroupId); + } + final int sourceIndex = sourceGroup.identifier; + + CommunicationGroup targetGroup = groupMap.get(targetRepresentative); + if (targetGroup == null) { + // create on-demand with the next larger group id + targetGroup = createAndStoreGroup(targetRepresentative, ++maxGroupId); + } + final int targetIndex = targetGroup.identifier; + + if (sourceIndex <= targetIndex) { + // indices obey current topological ordering + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + postProcessGroup(sourceGroup); + if (sourceGroup != targetGroup) { + postProcessGroup(targetGroup); + } + } else if (sourceIndex > targetIndex && !targetHadOutgoingEdges) { + // indices violate current topological ordering, but we can simply bump the target index + final boolean wasEnqueued = targetGroup.isEnqueued; + if (wasEnqueued) { + groupQueue.remove(targetGroup); + } + targetGroup.identifier = ++maxGroupId; + if (wasEnqueued) { + groupQueue.add(targetGroup); + } + + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + postProcessGroup(sourceGroup); + postProcessGroup(targetGroup); + } else { + // needs a full re-computation because of more complex change + precomputeGroups(); + } + } + } + + /** + * Returns true if the given {@link Node} is in a recursive {@link CommunicationGroup}, false otherwise. + */ + public boolean isInRecursiveGroup(final Node node) { + final CommunicationGroup group = this.getGroup(node); + if (group == null) { + return false; + } else { + return group.isRecursive(); + } + } + + /** + * Returns true if the given two {@link Node}s are in the same {@link CommunicationGroup}. + */ + public boolean areInSameGroup(final Node left, final Node right) { + final CommunicationGroup leftGroup = this.getGroup(left); + final CommunicationGroup rightGroup = this.getGroup(right); + return leftGroup != null && leftGroup == rightGroup; + } + + /** + * Unregisters a dependency between source and target. + * + * @param source + * the source node + * @param target + * the target node + */ + public void unregisterDependency(final Node source, final Node target) { + // delete the edge first, and then query the SCC info provider + this.dependencyGraph.deleteEdgeIfExists(source, target); + + final Node sourceRepresentative = sccInformationProvider.getRepresentative(source); + final Node targetRepresentative = sccInformationProvider.getRepresentative(target); + + // if they are still in the same SCC, + // then this deletion did not affect the SCCs, + // and it is sufficient to recompute affected fall-through flags; + // otherwise, we need a new pre-computation for the groupMap and groupQueue + if (sourceRepresentative.equals(targetRepresentative)) { + // this deletion could not have affected the split flags + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + } else { + // preComputeGroups takes care of the split flag maintenance + precomputeGroups(); + } + } + + /** + * Refresh fall-through flags if dependencies change for given target, but no SCC change + */ + private void refreshFallThroughFlag(final Node target) { + precomputeFallThroughFlag(target); + if (target instanceof DualInputNode) { + for (final Node indirectTarget : dependencyGraph.getTargetNodes(target).distinctValues()) { + precomputeFallThroughFlag(indirectTarget); + } + } + } + + /** + * Returns true if the given source-target edge in the communication network acts as a recursion cut point. + * The current implementation considers edges leading into {@link ProductionNode}s as cut point iff + * both source and target belong to the same group. + * + * @param source the source node + * @param target the target node + * @return true if the edge is a cut point, false otherwise + * @since 2.4 + */ + protected boolean isRecursionCutPoint(final Node source, final Node target) { + final Node effectiveSource = source instanceof SpecializedProjectionIndexer + ? ((SpecializedProjectionIndexer) source).getActiveNode() + : source; + final CommunicationGroup sourceGroup = this.getGroup(effectiveSource); + final CommunicationGroup targetGroup = this.getGroup(target); + return sourceGroup != null && sourceGroup == targetGroup && target instanceof ProductionNode; + } + + /** + * This hook allows concrete tracker implementations to perform tracker-specific post processing on nodes (cf. + * {@link NetworkStructureChangeSensitiveNode} and {@link BehaviorChangingMailbox}). At the time of the invocation, + * the network topology has already been updated. + */ + protected abstract void postProcessNode(final Node node); + + /** + * This hook allows concrete tracker implementations to perform tracker-specific post processing on groups. At the + * time of the invocation, the network topology has already been updated. + * @since 2.4 + */ + protected abstract void postProcessGroup(final CommunicationGroup group); + + /** + * Creates a proxy for the given {@link Mailbox} for the given requester {@link Node}. The proxy creation is + * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used + * to create {@link TimelyMailboxProxy}s depending on the network topology. There is no guarantee that the same + * proxy instance is returned when this method is called multiple times with the same arguments. + */ + public abstract Mailbox proxifyMailbox(final Node requester, final Mailbox original); + + /** + * Creates a proxy for the given {@link IndexerListener} for the given requester {@link Node}. The proxy creation is + * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used + * to create {@link TimelyIndexerListenerProxy}s depending on the network topology. There is no guarantee that the + * same proxy instance is returned when this method is called multiple times with the same arguments. + */ + public abstract IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +/** + * Subclasses of this interface represent meta data of update messages in Rete. + * + * @author Tamas Szabo + * @since 2.3 + */ +public interface MessageSelector { + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.Comparator; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; + +/** + * @since 2.4 + */ +public class NodeComparator implements Comparator { + + protected final Map nodeMap; + + public NodeComparator(final Map nodeMap) { + this.nodeMap = nodeMap; + } + + @Override + public int compare(final Node left, final Node right) { + return this.nodeMap.get(left) - this.nodeMap.get(right); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +/** + * A default message selector that can be used to associate phases to messages. + * + * @author Tamas Szabo + * @since 2.3 + */ +public enum PhasedSelector implements MessageSelector { + + /** + * No special distinguishing feature + */ + DEFAULT, + + /** + * Inserts and delete-insert monotone change pairs + */ + MONOTONE, + + /** + * Deletes + */ + ANTI_MONOTONE + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; + +/** + * A timestamp associated with update messages in timely evaluation. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class Timestamp implements Comparable, MessageSelector { + + protected final int value; + public static final Timestamp ZERO = new Timestamp(0); + /** + * @since 2.4 + */ + public static final Timeline INSERT_AT_ZERO_TIMELINE = Timelines.createFrom(Timestamp.ZERO); + + public Timestamp(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public Timestamp max(final Timestamp that) { + if (this.value >= that.value) { + return this; + } else { + return that; + } + } + + /** + * @since 2.4 + */ + public Timestamp min(final Timestamp that) { + if (this.value <= that.value) { + return this; + } else { + return that; + } + } + + @Override + public int compareTo(final Timestamp that) { + return this.value - that.value; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || !(obj instanceof Timestamp)) { + return false; + } else { + return this.value == ((Timestamp) obj).value; + } + } + + @Override + public int hashCode() { + return this.value; + } + + @Override + public String toString() { + return Integer.toString(this.value); + } + + /** + * A {@link Map} implementation that associates the zero timestamp with every key. There is no suppor for + * {@link Map#entrySet()} due to performance reasons. + * + * @author Tamas Szabo + */ + public static final class AllZeroMap extends AbstractMap> { + + private final Collection wrapped; + + public AllZeroMap(Set wrapped) { + this.wrapped = wrapped; + } + + @Override + public Set>> entrySet() { + throw new UnsupportedOperationException("Use the combination of keySet() and get()!"); + } + + /** + * @since 2.4 + */ + @Override + public Timeline get(final Object key) { + return INSERT_AT_ZERO_TIMELINE; + } + + @Override + public Set keySet() { + return (Set) this.wrapped; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ": " + this.keySet().toString(); + } + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group representing either a single node where the + * node is a monotonicity aware one or a set of nodes that form an SCC. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class RecursiveCommunicationGroup extends CommunicationGroup { + + private final Set antiMonotoneMailboxes; + private final Set monotoneMailboxes; + private final Set defaultMailboxes; + private final Set rederivables; + private boolean currentlyDelivering; + + /** + * @since 1.7 + */ + public RecursiveCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + super(tracker, representative, identifier); + this.antiMonotoneMailboxes = CollectionsFactory.createSet(); + this.monotoneMailboxes = CollectionsFactory.createSet(); + this.defaultMailboxes = CollectionsFactory.createSet(); + this.rederivables = new LinkedHashSet(); + this.currentlyDelivering = false; + } + + @Override + public void deliverMessages() { + this.currentlyDelivering = true; + + // ANTI-MONOTONE PHASE + while (!this.antiMonotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) { + while (!this.antiMonotoneMailboxes.isEmpty()) { + final Mailbox mailbox = this.antiMonotoneMailboxes.iterator().next(); + this.antiMonotoneMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.ANTI_MONOTONE); + } + while (!this.defaultMailboxes.isEmpty()) { + final Mailbox mailbox = this.defaultMailboxes.iterator().next(); + this.defaultMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.DEFAULT); + } + } + + // REDERIVE PHASE + while (!this.rederivables.isEmpty()) { + // re-derivable nodes take care of their unregistration!! + final RederivableNode node = this.rederivables.iterator().next(); + node.rederiveOne(); + } + + // MONOTONE PHASE + while (!this.monotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) { + while (!this.monotoneMailboxes.isEmpty()) { + final Mailbox mailbox = this.monotoneMailboxes.iterator().next(); + this.monotoneMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.MONOTONE); + } + while (!this.defaultMailboxes.isEmpty()) { + final Mailbox mailbox = this.defaultMailboxes.iterator().next(); + this.defaultMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.DEFAULT); + } + } + + this.currentlyDelivering = false; + } + + @Override + public boolean isEmpty() { + return this.rederivables.isEmpty() && this.antiMonotoneMailboxes.isEmpty() + && this.monotoneMailboxes.isEmpty() && this.defaultMailboxes.isEmpty(); + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) { + final Collection mailboxes = getMailboxContainer(kind); + mailboxes.add(mailbox); + if (!this.isEnqueued && !this.currentlyDelivering) { + this.tracker.activateUnenqueued(this); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + final Collection mailboxes = getMailboxContainer(kind); + mailboxes.remove(mailbox); + if (isEmpty()) { + this.tracker.deactivate(this); + } + } + + private Collection getMailboxContainer(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + return this.antiMonotoneMailboxes; + } else if (kind == PhasedSelector.MONOTONE) { + return this.monotoneMailboxes; + } else if (kind == PhasedSelector.DEFAULT) { + return this.defaultMailboxes; + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + public void addRederivable(final RederivableNode node) { + this.rederivables.add(node); + if (!this.isEnqueued) { + this.tracker.activateUnenqueued(this); + } + } + + public void removeRederivable(final RederivableNode node) { + this.rederivables.remove(node); + if (isEmpty()) { + this.tracker.deactivate(this); + } + } + + public Collection getRederivables() { + return this.rederivables; + } + + @Override + public Map> getMailboxes() { + Map> map = new EnumMap<>(PhasedSelector.class); + map.put(PhasedSelector.ANTI_MONOTONE, antiMonotoneMailboxes); + map.put(PhasedSelector.MONOTONE, monotoneMailboxes); + map.put(PhasedSelector.DEFAULT, defaultMailboxes); + return Collections.unmodifiableMap(map); + } + + @Override + public boolean isRecursive() { + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group containing only a single node with a single default + * mailbox. + * + * @author Tamas Szabo + * @since 1.6 + */ +public class SingletonCommunicationGroup extends CommunicationGroup { + + private Mailbox mailbox; + + /** + * @since 1.7 + */ + public SingletonCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + super(tracker, representative, identifier); + } + + @Override + public void deliverMessages() { + this.mailbox.deliverAll(PhasedSelector.DEFAULT); + } + + @Override + public boolean isEmpty() { + return this.mailbox == null; + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + this.mailbox = mailbox; + if (!this.isEnqueued) { + this.tracker.activateUnenqueued(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + this.mailbox = null; + this.tracker.deactivate(this); + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public Map> getMailboxes() { + if (mailbox != null) { + return Collections.singletonMap(PhasedSelector.DEFAULT, Collections.singleton(mailbox)); + } else { + return Collections.emptyMap(); + } + } + + @Override + public boolean isRecursive() { + return false; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; + +/** + * Timeless implementation of the communication tracker. + * + * @author Tamas Szabo + * @since 2.2 + */ +public class TimelessCommunicationTracker extends CommunicationTracker { + + @Override + protected CommunicationGroup createGroup(Node representative, int index) { + final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1; + final boolean isReceiver = representative instanceof Receiver; + final boolean isPosetIndifferent = isReceiver + && ((Receiver) representative).getMailbox() instanceof BehaviorChangingMailbox; + final boolean isSingletonInDRedMode = isSingleton && (representative instanceof RederivableNode) + && ((RederivableNode) representative).isInDRedMode(); + + CommunicationGroup group = null; + // we can only use a singleton group iff + // (1) the SCC has one node AND + // (2) either we have a poset-indifferent mailbox OR the node is not even a receiver AND + // (3) the node does not run in DRed mode in a singleton group + if (isSingleton && (isPosetIndifferent || !isReceiver) && !isSingletonInDRedMode) { + group = new SingletonCommunicationGroup(this, representative, index); + } else { + group = new RecursiveCommunicationGroup(this, representative, index); + } + + return group; + } + + @Override + protected void reconstructQueueContents(final Set oldActiveGroups) { + for (final CommunicationGroup oldGroup : oldActiveGroups) { + for (final Entry> entry : oldGroup.getMailboxes().entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver()); + newGroup.notifyHasMessage(mailbox, entry.getKey()); + } + } + + if (oldGroup instanceof RecursiveCommunicationGroup) { + for (final RederivableNode node : ((RecursiveCommunicationGroup) oldGroup).getRederivables()) { + final CommunicationGroup newGroup = this.groupMap.get(node); + if (!(newGroup instanceof RecursiveCommunicationGroup)) { + throw new IllegalStateException("The new group must also be recursive! " + newGroup); + } + ((RecursiveCommunicationGroup) newGroup).addRederivable(node); + } + } + } + } + + @Override + public Mailbox proxifyMailbox(final Node requester, final Mailbox original) { + return original; + } + + @Override + public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) { + return original; + } + + @Override + protected void postProcessNode(final Node node) { + if (node instanceof Receiver) { + final Mailbox mailbox = ((Receiver) node).getMailbox(); + if (mailbox instanceof BehaviorChangingMailbox) { + final CommunicationGroup group = this.groupMap.get(node); + final Set sccNodes = this.sccInformationProvider.sccs.getPartition(node); + // a default mailbox must split its messages iff + // (1) its receiver is in a recursive group and + final boolean c1 = group.isRecursive(); + // (2) its receiver is at the SCC boundary of that group + final boolean c2 = isAtSCCBoundary(node); + // (3) its group consists of more than one node + final boolean c3 = sccNodes.size() > 1; + ((BehaviorChangingMailbox) mailbox).setSplitFlag(c1 && c2 && c3); + } + } + } + + @Override + protected void postProcessGroup(final CommunicationGroup group) { + + } + + /** + * @since 2.0 + */ + private boolean isAtSCCBoundary(final Node node) { + final CommunicationGroup ownGroup = this.groupMap.get(node); + assert ownGroup != null; + for (final Node source : this.dependencyGraph.getSourceNodes(node).distinctValues()) { + final Set sourcesToCheck = new HashSet(); + sourcesToCheck.add(source); + // DualInputNodes must be checked additionally because they do not use a mailbox directly. + // It can happen that their indexers actually belong to other SCCs. + if (source instanceof DualInputNode) { + final DualInputNode dualInput = (DualInputNode) source; + final IterableIndexer primarySlot = dualInput.getPrimarySlot(); + if (primarySlot != null) { + sourcesToCheck.add(primarySlot.getActiveNode()); + } + final Indexer secondarySlot = dualInput.getSecondarySlot(); + if (secondarySlot != null) { + sourcesToCheck.add(secondarySlot.getActiveNode()); + } + } + for (final Node current : sourcesToCheck) { + final CommunicationGroup otherGroup = this.groupMap.get(current); + assert otherGroup != null; + if (!ownGroup.equals(otherGroup)) { + return true; + } + } + } + return false; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * {@link Node}s that implement this interface can resume folding of their states when instructed during timely evaluation. + * + * @since 2.3 + * @author Tamas Szabo + */ +public interface ResumableNode extends Node, IGroupable { + + /** + * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to + * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more + * folding to do towards higher timestamps. + */ + public void resumeAt(final Timestamp timestamp); + + /** + * Returns the smallest timestamp where lazy folding shall be resumed, or null if there is no more folding to do in this + * resumable. + */ + public Timestamp getResumableTimestamp(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * A timely communication group implementation. {@link TimelyMailbox}es and {@link LazyFoldingNode}s are ordered in the + * increasing order of timestamps. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyCommunicationGroup extends CommunicationGroup { + + private final boolean isSingleton; + private final TreeMap> mailboxQueue; + // may be null - only used in the scattered case where we need to take care of mailboxes and resumables too + private Comparator nodeComparator; + private boolean currentlyDelivering; + private Timestamp currentlyDeliveredTimestamp; + + public TimelyCommunicationGroup(final TimelyCommunicationTracker tracker, final Node representative, + final int identifier, final boolean isSingleton) { + super(tracker, representative, identifier); + this.isSingleton = isSingleton; + this.mailboxQueue = CollectionsFactory.createTreeMap(); + this.currentlyDelivering = false; + } + + /** + * Sets the {@link Comparator} to be used to order the {@link Mailbox}es at a given {@link Timestamp} in the mailbox + * queue. Additionally, reorders already queued {@link Mailbox}es to reflect the new comparator. The comparator may + * be null, in this case, no set ordering will be enforced among the {@link Mailbox}es. + */ + public void setComparatorAndReorderMailboxes(final Comparator nodeComparator) { + this.nodeComparator = nodeComparator; + if (!this.mailboxQueue.isEmpty()) { + final HashMap> queueCopy = new HashMap>(this.mailboxQueue); + this.mailboxQueue.clear(); + for (final Entry> entry : queueCopy.entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + this.notifyHasMessage(mailbox, entry.getKey()); + } + } + } + } + + @Override + public void deliverMessages() { + this.currentlyDelivering = true; + while (!this.mailboxQueue.isEmpty()) { + // care must be taken here how we iterate over the mailboxes + // it is not okay to loop over the mailboxes at once because a mailbox may disappear from the collection as + // a result of delivering messages from another mailboxes under the same timestamp + // because of this, it is crucial that we pick the mailboxes one by one + final Entry> entry = this.mailboxQueue.firstEntry(); + final Timestamp timestamp = entry.getKey(); + final Set mailboxes = entry.getValue(); + final Mailbox mailbox = mailboxes.iterator().next(); + mailboxes.remove(mailbox); + if (mailboxes.isEmpty()) { + this.mailboxQueue.pollFirstEntry(); + } + assert mailbox instanceof TimelyMailbox; + /* debug */ this.currentlyDeliveredTimestamp = timestamp; + mailbox.deliverAll(timestamp); + /* debug */ this.currentlyDeliveredTimestamp = null; + } + this.currentlyDelivering = false; + } + + @Override + public boolean isEmpty() { + return this.mailboxQueue.isEmpty(); + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, MessageSelector kind) { + if (kind instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) kind; + if (Options.MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS) { + if (timestamp.compareTo(this.currentlyDeliveredTimestamp) < 0) { + final Logger logger = this.representative.getContainer().getNetwork().getEngine().getLogger(); + logger.error( + "[INTERNAL ERROR] Violation of differential dataflow communication schema! The communication component with representative " + + this.representative + " observed decreasing timestamp during message delivery!"); + } + } + final Set mailboxes = this.mailboxQueue.computeIfAbsent(timestamp, k -> { + if (this.nodeComparator == null) { + return CollectionsFactory.createSet(); + } else { + return new TreeSet(new Comparator() { + @Override + public int compare(final Mailbox left, final Mailbox right) { + return nodeComparator.compare(left.getReceiver(), right.getReceiver()); + } + }); + } + }); + mailboxes.add(mailbox); + if (!this.isEnqueued && !this.currentlyDelivering) { + this.tracker.activateUnenqueued(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + if (kind instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) kind; + this.mailboxQueue.compute(timestamp, (k, v) -> { + if (v == null) { + throw new IllegalStateException("No mailboxes registered at timestamp " + timestamp + "!"); + } + if (!v.remove(mailbox)) { + throw new IllegalStateException( + "The mailbox " + mailbox + " was not registered at timestamp " + timestamp + "!"); + } + if (v.isEmpty()) { + return null; + } else { + return v; + } + }); + if (this.mailboxQueue.isEmpty()) { + this.tracker.deactivate(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public Map> getMailboxes() { + return Collections.unmodifiableMap(this.mailboxQueue); + } + + @Override + public boolean isRecursive() { + return !this.isSingleton; + } + +} 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..1ff69882 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.NodeComparator; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.single.DiscriminatorDispatcherNode; + +/** + * Timely (DDF) implementation of the {@link CommunicationTracker}. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyCommunicationTracker extends CommunicationTracker { + + protected final TimelyConfiguration configuration; + + public TimelyCommunicationTracker(final TimelyConfiguration configuration) { + this.configuration = configuration; + } + + @Override + protected CommunicationGroup createGroup(final Node representative, final int index) { + final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1; + return new TimelyCommunicationGroup(this, representative, index, isSingleton); + } + + @Override + protected void reconstructQueueContents(final Set oldActiveGroups) { + for (final CommunicationGroup oldGroup : oldActiveGroups) { + for (final Entry> entry : oldGroup.getMailboxes().entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver()); + newGroup.notifyHasMessage(mailbox, entry.getKey()); + } + } + } + } + + @Override + public Mailbox proxifyMailbox(final Node requester, final Mailbox original) { + final Mailbox mailboxToProxify = (original instanceof TimelyMailboxProxy) + ? ((TimelyMailboxProxy) original).getWrappedMailbox() + : original; + final TimestampTransformation preprocessor = getPreprocessor(requester, mailboxToProxify.getReceiver()); + if (preprocessor == null) { + return mailboxToProxify; + } else { + return new TimelyMailboxProxy(mailboxToProxify, preprocessor); + } + } + + @Override + public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) { + final IndexerListener listenerToProxify = (original instanceof TimelyIndexerListenerProxy) + ? ((TimelyIndexerListenerProxy) original).getWrappedIndexerListener() + : original; + final TimestampTransformation preprocessor = getPreprocessor(requester, listenerToProxify.getOwner()); + if (preprocessor == null) { + return listenerToProxify; + } else { + return new TimelyIndexerListenerProxy(listenerToProxify, preprocessor); + } + } + + protected TimestampTransformation getPreprocessor(final Node source, final Node target) { + final Node effectiveSource = source instanceof SpecializedProjectionIndexer + ? ((SpecializedProjectionIndexer) source).getActiveNode() + : source; + final CommunicationGroup sourceGroup = this.getGroup(effectiveSource); + final CommunicationGroup targetGroup = this.getGroup(target); + + if (sourceGroup != null && targetGroup != null) { + // during RETE construction, the groups may be still null + if (sourceGroup != targetGroup && sourceGroup.isRecursive()) { + // targetGroup is a successor SCC of sourceGroup + // and sourceGroup is a recursive SCC + // then we need to zero out the timestamps + return TimestampTransformation.RESET; + } + if (sourceGroup == targetGroup && target instanceof ProductionNode) { + // if requester and receiver are in the same SCC + // and receiver is a production node + // then we need to increment the timestamps + return TimestampTransformation.INCREMENT; + } + } + + return null; + } + + @Override + protected void postProcessNode(final Node node) { + if (node instanceof NetworkStructureChangeSensitiveNode) { + ((NetworkStructureChangeSensitiveNode) node).networkStructureChanged(); + } + } + + @Override + protected void postProcessGroup(final CommunicationGroup group) { + if (this.configuration.getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) { + final Node representative = group.getRepresentative(); + final Set groupMembers = this.sccInformationProvider.sccs.getPartition(representative); + if (groupMembers.size() > 1) { + final Graph graph = new Graph(); + + for (final Node node : groupMembers) { + graph.insertNode(node); + } + + for (final Node source : groupMembers) { + for (final Node target : this.dependencyGraph.getTargetNodes(source)) { + // (1) the edge is not a recursion cut point + // (2) the edge is within this group + if (!this.isRecursionCutPoint(source, target) && groupMembers.contains(target)) { + graph.insertEdge(source, target); + } + } + } + + final List orderedNodes = TopologicalSorting.compute(graph); + final Map nodeMap = CollectionsFactory.createMap(); + int identifier = 0; + for (final Node orderedNode : orderedNodes) { + nodeMap.put(orderedNode, identifier++); + } + + ((TimelyCommunicationGroup) group).setComparatorAndReorderMailboxes(new NodeComparator(nodeMap)); + } + } + } + + /** + * This static field is used for debug purposes in the DotGenerator. + */ + public static final Function> EDGE_LABEL_FUNCTION = new Function>() { + + @Override + public Function apply(final Node source) { + return new Function() { + @Override + public String apply(final Node target) { + if (source instanceof SpecializedProjectionIndexer) { + final Collection subscriptions = ((SpecializedProjectionIndexer) source) + .getSubscriptions(); + for (final ListenerSubscription subscription : subscriptions) { + if (subscription.getListener().getOwner() == target + && subscription.getListener() instanceof TimelyIndexerListenerProxy) { + return ((TimelyIndexerListenerProxy) subscription.getListener()).preprocessor + .toString(); + } + } + } + if (source instanceof StandardIndexer) { + final Collection listeners = ((StandardIndexer) source).getListeners(); + for (final IndexerListener listener : listeners) { + if (listener.getOwner() == target && listener instanceof TimelyIndexerListenerProxy) { + return ((TimelyIndexerListenerProxy) listener).preprocessor.toString(); + } + } + } + if (source instanceof StandardNode) { + final Collection mailboxes = ((StandardNode) source).getChildMailboxes(); + for (final Mailbox mailbox : mailboxes) { + if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) { + return ((TimelyMailboxProxy) mailbox).preprocessor.toString(); + } + } + } + if (source instanceof DiscriminatorDispatcherNode) { + final Collection mailboxes = ((DiscriminatorDispatcherNode) source) + .getBucketMailboxes().values(); + for (final Mailbox mailbox : mailboxes) { + if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) { + return ((TimelyMailboxProxy) mailbox).preprocessor.toString(); + } + } + } + return null; + } + }; + } + + }; + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A timely proxy for another {@link IndexerListener}, which performs some preprocessing + * on the differential timestamps before passing it on to the real recipient. + *

+ * These proxies are used on edges leading into {@link ProductionNode}s. Because {@link ProductionNode}s + * never ask back the indexer for its contents, there is no need to also apply the proxy on that direction. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyIndexerListenerProxy implements IndexerListener { + + protected final TimestampTransformation preprocessor; + protected final IndexerListener wrapped; + + public TimelyIndexerListenerProxy(final IndexerListener wrapped, + final TimestampTransformation preprocessor) { + Preconditions.checkArgument(!(wrapped instanceof TimelyIndexerListenerProxy), "Proxy in a proxy is not allowed!"); + this.wrapped = wrapped; + this.preprocessor = preprocessor; + } + + public IndexerListener getWrappedIndexerListener() { + return wrapped; + } + + @Override + public Node getOwner() { + return this.wrapped.getOwner(); + } + + @Override + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp) { + this.wrapped.notifyIndexerUpdate(direction, updateElement, signature, change, preprocessor.process(timestamp)); + } + + @Override + public String toString() { + return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } else if (obj == this) { + return true; + } else { + final TimelyIndexerListenerProxy that = (TimelyIndexerListenerProxy) obj; + return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor; + } + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 17 + this.wrapped.hashCode(); + hash = hash * 31 + this.preprocessor.hashCode(); + return hash; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A timely proxy for another {@link Mailbox}, which performs some preprocessing + * on the differential timestamps before passing it on to the real recipient. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyMailboxProxy implements Mailbox { + + protected final TimestampTransformation preprocessor; + protected final Mailbox wrapped; + + public TimelyMailboxProxy(final Mailbox wrapped, final TimestampTransformation preprocessor) { + Preconditions.checkArgument(!(wrapped instanceof TimelyMailboxProxy), "Proxy in a proxy is not allowed!"); + this.wrapped = wrapped; + this.preprocessor = preprocessor; + } + + public Mailbox getWrappedMailbox() { + return wrapped; + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + this.wrapped.postMessage(direction, update, preprocessor.process(timestamp)); + } + + @Override + public String toString() { + return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString(); + } + + @Override + public void clear() { + this.wrapped.clear(); + } + + @Override + public void deliverAll(final MessageSelector selector) { + this.wrapped.deliverAll(selector); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.wrapped.getCurrentGroup(); + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.wrapped.setCurrentGroup(group); + } + + @Override + public Receiver getReceiver() { + return this.wrapped.getReceiver(); + } + + @Override + public boolean isEmpty() { + return this.wrapped.isEmpty(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } else if (obj == this) { + return true; + } else { + final TimelyMailboxProxy that = (TimelyMailboxProxy) obj; + return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor; + } + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 17 + this.wrapped.hashCode(); + hash = hash * 31 + this.preprocessor.hashCode(); + return hash; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Values of this enum perform different kind of preprocessing on {@link Timestamp}s. + * This is used on edges leading in and out from {@link Node}s in recursive {@link TimelyCommunicationGroup}s. + * + * @author Tamas Szabo + * @since 2.3 + */ +public enum TimestampTransformation { + + INCREMENT { + @Override + public Timestamp process(final Timestamp timestamp) { + return new Timestamp(timestamp.getValue() + 1); + } + + @Override + public String toString() { + return "INCREMENT"; + } + }, + RESET { + @Override + public Timestamp process(final Timestamp timestamp) { + return Timestamp.ZERO; + } + + @Override + public String toString() { + return "RESET"; + } + }; + + public abstract Timestamp process(final Timestamp timestamp); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Instances of this class are responsible for initializing a {@link Receiver} with the contents of a {@link Supplier}. + * However, due to the dynamic nature of the Rete {@link Network} and to the fact that certain {@link Node}s in the + * {@link Network} are sensitive to the shape of the {@link Network}, the commands must be delayed until the + * construction of the {@link Network} has stabilized. + * + * @author Tamas Szabo + * @since 2.3 + */ +public abstract class DelayedCommand implements Runnable { + + protected final Supplier supplier; + protected final Receiver receiver; + protected final Direction direction; + protected final ReteContainer container; + + public DelayedCommand(final Supplier supplier, final Receiver receiver, final Direction direction, + final ReteContainer container) { + this.supplier = supplier; + this.receiver = receiver; + this.direction = direction; + this.container = container; + } + + @Override + public void run() { + final CommunicationTracker tracker = this.container.getCommunicationTracker(); + final Mailbox mailbox = tracker.proxifyMailbox(this.supplier, this.receiver.getMailbox()); + + if (this.isTimestampAware()) { + final Map> contents = this.container.pullContentsWithTimeline(this.supplier, + false); + for (final Entry> entry : contents.entrySet()) { + for (final Signed change : entry.getValue().asChangeSequence()) { + mailbox.postMessage(change.getDirection().multiply(this.direction), entry.getKey(), + change.getPayload()); + } + } + } else { + final Collection contents = this.container.pullContents(this.supplier, false); + for (final Tuple tuple : contents) { + mailbox.postMessage(this.direction, tuple, Timestamp.ZERO); + } + } + } + + @Override + public String toString() { + return this.supplier + " -> " + this.receiver.toString(); + } + + protected abstract boolean isTimestampAware(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +public class DelayedConnectCommand extends DelayedCommand { + + public DelayedConnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container) { + super(supplier, receiver, Direction.INSERT, container); + } + + @Override + protected boolean isTimestampAware() { + return this.container.isTimelyEvaluation() && this.container.getCommunicationTracker().areInSameGroup(this.supplier, this.receiver); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +public class DelayedDisconnectCommand extends DelayedCommand { + + protected final boolean wasInSameSCC; + + public DelayedDisconnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container, final boolean wasInSameSCC) { + super(supplier, receiver, Direction.DELETE, container); + this.wasInSameSCC = wasInSameSCC; + } + + @Override + protected boolean isTimestampAware() { + return this.wasInSameSCC; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public class DefaultMessageIndexer implements MessageIndexer { + + protected final Map indexer; + + public DefaultMessageIndexer() { + this.indexer = CollectionsFactory.createMap(); + } + + public Map getTuples() { + return Collections.unmodifiableMap(this.indexer); + } + + @Override + public int getCount(final Tuple update) { + final Integer count = getTuples().get(update); + if (count == null) { + return 0; + } else { + return count; + } + } + + @Override + public void insert(final Tuple update) { + update(update, 1); + } + + @Override + public void delete(final Tuple update) { + update(update, -1); + } + + @Override + public void update(final Tuple update, final int delta) { + final Integer oldCount = this.indexer.get(update); + final int newCount = (oldCount == null ? 0 : oldCount) + delta; + if (newCount == 0) { + this.indexer.remove(update); + } else { + this.indexer.put(update, newCount); + } + } + + @Override + public boolean isEmpty() { + return this.indexer.isEmpty(); + } + + @Override + public void clear() { + this.indexer.clear(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public class GroupBasedMessageIndexer implements MessageIndexer { + + protected final Map indexer; + protected final TupleMask groupMask; + + public GroupBasedMessageIndexer(final TupleMask groupMask) { + this.indexer = CollectionsFactory.createMap(); + this.groupMask = groupMask; + } + + public Map getTuplesByGroup(final Tuple group) { + final DefaultMessageIndexer values = this.indexer.get(group); + if (values == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(values.getTuples()); + } + } + + @Override + public int getCount(final Tuple update) { + final Tuple group = this.groupMask.transform(update); + final Integer count = getTuplesByGroup(group).get(update); + if (count == null) { + return 0; + } else { + return count; + } + } + + public Set getGroups() { + return Collections.unmodifiableSet(this.indexer.keySet()); + } + + @Override + public void insert(final Tuple update) { + update(update, 1); + } + + @Override + public void delete(final Tuple update) { + update(update, -1); + } + + @Override + public void update(final Tuple update, final int delta) { + final Tuple group = this.groupMask.transform(update); + DefaultMessageIndexer valueIndexer = this.indexer.get(group); + + if (valueIndexer == null) { + valueIndexer = new DefaultMessageIndexer(); + this.indexer.put(group, valueIndexer); + } + + valueIndexer.update(update, delta); + + // it may happen that the indexer becomes empty as a result of the update + if (valueIndexer.isEmpty()) { + this.indexer.remove(group); + } + } + + @Override + public boolean isEmpty() { + return this.indexer.isEmpty(); + } + + @Override + public void clear() { + this.indexer.clear(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A message indexer is used by {@link Mailbox}es to index their contents. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface MessageIndexer extends Clearable { + + public void insert(final Tuple update); + + public void delete(final Tuple update); + + public void update(final Tuple update, final int delta); + + public boolean isEmpty(); + + public int getCount(final Tuple update); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; + +/** + * An adaptable mailbox can be wrapped by another mailbox to act in behalf of that. The significance of the adaptation + * is that the adaptee will notify the {@link CommunicationTracker} about updates by promoting the adapter itself. + * Adaptable mailboxes are used by the {@link BehaviorChangingMailbox}. + * + * Compare this with {@link TimelyMailboxProxy}. That one also wraps another mailbox in order to + * perform preprocessing on the messages sent to the original recipient. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface AdaptableMailbox extends Mailbox { + + public Mailbox getAdapter(); + + public void setAdapter(final Mailbox adapter); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; + +/** + * A fall through capable mailbox can directly call the update method of its {@link Receiver} instead of using the + * standard post-deliver mailbox semantics. If the fall through flag is set to true, the mailbox uses direct delivery, + * otherwise it operates in the original behavior. The fall through operation is preferable whenever applicable because + * it improves performance. The fall through flag is controlled by the {@link CommunicationTracker} based on the + * receiver node type and network topology. + * + * @author Tamas Szabo + * @since 2.2 + */ +public interface FallThroughCapableMailbox extends Mailbox { + + public boolean isFallThrough(); + + public void setFallThrough(final boolean fallThrough); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A mailbox is associated with every {@link Receiver}. Messages can be sent to a {@link Receiver} by posting them into + * the mailbox. Different mailbox implementations may differ in the way how they deliver the posted messages. + * + * @author Tamas Szabo + * @since 2.0 + * + */ +public interface Mailbox extends Clearable, IGroupable { + + /** + * Posts a new message to this mailbox. + * + * @param direction + * the direction of the update + * @param update + * the update element + * @since 2.4 + */ + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp); + + /** + * Delivers all messages according to the given selector from this mailbox. The selector can also be null. In this case, no + * special separation is expected between the messages. + * + * @param selector the message selector + */ + public void deliverAll(final MessageSelector selector); + + /** + * Returns the {@link Receiver} of this mailbox. + * + * @return the receiver + */ + public Receiver getReceiver(); + + /** + * Returns the {@link CommunicationGroup} of the receiver of this mailbox. + * + * @return the communication group + */ + public CommunicationGroup getCurrentGroup(); + + /** + * Sets the {@link CommunicationGroup} that the receiver of this mailbox is associated with. + * + * @param group + * the communication group + */ + public void setCurrentGroup(final CommunicationGroup group); + + /** + * Returns true if this mailbox is empty. + * + * @return + */ + public boolean isEmpty(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer; + +/** + * A factory used to create message indexers for {@link Mailbox}es. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface MessageIndexerFactory { + + public I create(); + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.MessageIndexerFactory; + +/** + * An abstract mailbox implementation that is capable of splitting update messages based on some form of monotonicity + * (anti-monotone and monotone). The monotonicity is either defined by the less or equal operator of a poset or, it can + * be the standard subset ordering among sets of tuples. + * + * @author Tamas Szabo + * @since 2.0 + * + */ +public abstract class AbstractUpdateSplittingMailbox implements Mailbox { + + protected IndexerType monotoneQueue; + protected IndexerType antiMonotoneQueue; + protected IndexerType monotoneBuffer; + protected IndexerType antiMonotoneBuffer; + protected boolean deliveringMonotone; + protected boolean deliveringAntiMonotone; + protected final ReceiverType receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + + public AbstractUpdateSplittingMailbox(final ReceiverType receiver, final ReteContainer container, + final MessageIndexerFactory factory) { + this.receiver = receiver; + this.container = container; + this.monotoneQueue = factory.create(); + this.antiMonotoneQueue = factory.create(); + this.monotoneBuffer = factory.create(); + this.antiMonotoneBuffer = factory.create(); + this.deliveringMonotone = false; + this.deliveringAntiMonotone = false; + } + + protected void swapAndClearMonotone() { + final IndexerType tmp = this.monotoneQueue; + this.monotoneQueue = this.monotoneBuffer; + this.monotoneBuffer = tmp; + this.monotoneBuffer.clear(); + } + + protected void swapAndClearAntiMonotone() { + final IndexerType tmp = this.antiMonotoneQueue; + this.antiMonotoneQueue = this.antiMonotoneBuffer; + this.antiMonotoneBuffer = tmp; + this.antiMonotoneBuffer.clear(); + } + + protected IndexerType getActiveMonotoneQueue() { + if (this.deliveringMonotone) { + return this.monotoneBuffer; + } else { + return this.monotoneQueue; + } + } + + protected IndexerType getActiveAntiMonotoneQueue() { + if (this.deliveringAntiMonotone) { + return this.antiMonotoneBuffer; + } else { + return this.antiMonotoneQueue; + } + } + + @Override + public ReceiverType getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.monotoneQueue.clear(); + this.antiMonotoneQueue.clear(); + this.monotoneBuffer.clear(); + this.antiMonotoneBuffer.clear(); + } + + @Override + public boolean isEmpty() { + return this.getActiveMonotoneQueue().isEmpty() && this.getActiveAntiMonotoneQueue().isEmpty(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox; + +/** + * This mailbox changes its behavior based on the position of its {@link Receiver} in the network topology. + * It either behaves as a {@link DefaultMailbox} or as an {@link UpdateSplittingMailbox}. The decision is made by the + * {@link CommunicationTracker}, see {@link TimelessCommunicationTracker#postProcessNode(Node)} for more details. + * + * @author Tamas Szabo + */ +public class BehaviorChangingMailbox implements FallThroughCapableMailbox { + + protected boolean fallThrough; + protected boolean split; + protected AdaptableMailbox wrapped; + protected final Receiver receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + + public BehaviorChangingMailbox(final Receiver receiver, final ReteContainer container) { + this.fallThrough = false; + this.split = false; + this.receiver = receiver; + this.container = container; + this.wrapped = new DefaultMailbox(receiver, container); + this.wrapped.setAdapter(this); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + if (this.fallThrough && !this.container.isExecutingDelayedCommands()) { + // disable fall through while we are in the middle of executing delayed construction commands + this.receiver.update(direction, update, timestamp); + } else { + this.wrapped.postMessage(direction, update, timestamp); + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + this.wrapped.deliverAll(kind); + } + + @Override + public String toString() { + return "A_MBOX -> " + this.wrapped; + } + + public void setSplitFlag(final boolean splitValue) { + if (this.split != splitValue) { + assert isEmpty(); + if (splitValue) { + this.wrapped = new UpdateSplittingMailbox(this.receiver, this.container); + } else { + this.wrapped = new DefaultMailbox(this.receiver, this.container); + } + this.wrapped.setAdapter(this); + this.split = splitValue; + } + } + + @Override + public boolean isEmpty() { + return this.wrapped.isEmpty(); + } + + @Override + public void clear() { + this.wrapped.clear(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public boolean isFallThrough() { + return this.fallThrough; + } + + @Override + public void setFallThrough(final boolean fallThrough) { + this.fallThrough = fallThrough; + } + +} \ 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..5c72ba39 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Default mailbox implementation. + *

+ * Usually, the mailbox performs counting of messages so that they can cancel each other out. However, if marked as a + * fall-through mailbox, than update messages are delivered directly to the receiver node to reduce overhead. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class DefaultMailbox implements AdaptableMailbox { + + private static int SIZE_TRESHOLD = 127; + + protected Map queue; + protected Map buffer; + protected final Receiver receiver; + protected final ReteContainer container; + protected boolean delivering; + protected Mailbox adapter; + protected CommunicationGroup group; + + public DefaultMailbox(final Receiver receiver, final ReteContainer container) { + this.receiver = receiver; + this.container = container; + this.queue = CollectionsFactory.createMap(); + this.buffer = CollectionsFactory.createMap(); + this.adapter = this; + } + + protected Map getActiveQueue() { + if (this.delivering) { + return this.buffer; + } else { + return this.queue; + } + } + + @Override + public Mailbox getAdapter() { + return this.adapter; + } + + @Override + public void setAdapter(final Mailbox adapter) { + this.adapter = adapter; + } + + @Override + public boolean isEmpty() { + return getActiveQueue().isEmpty(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Map activeQueue = getActiveQueue(); + final boolean wasEmpty = activeQueue.isEmpty(); + + boolean significantChange = false; + Integer count = activeQueue.get(update); + if (count == null) { + count = 0; + significantChange = true; + } + + if (direction == Direction.DELETE) { + count--; + } else { + count++; + } + + if (count == 0) { + activeQueue.remove(update); + significantChange = true; + } else { + activeQueue.put(update, count); + } + + if (significantChange) { + final Mailbox targetMailbox = this.adapter; + final CommunicationGroup targetGroup = this.adapter.getCurrentGroup(); + + if (wasEmpty) { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.DEFAULT); + } else if (activeQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.DEFAULT); + } + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.delivering = true; + this.receiver.batchUpdate(this.queue.entrySet(), Timestamp.ZERO); + this.delivering = false; + + if (queue.size() > SIZE_TRESHOLD) { + this.queue = this.buffer; + this.buffer = CollectionsFactory.createMap(); + } else { + this.queue.clear(); + final Map tmpQueue = this.queue; + this.queue = this.buffer; + this.buffer = tmpQueue; + } + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "D_MBOX (" + this.receiver + ") " + this.getActiveQueue(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.queue.clear(); + this.buffer.clear(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.indexer.GroupBasedMessageIndexer; + +/** + * A monotonicity aware mailbox implementation. The mailbox uses an {@link IPosetComparator} to identify if a pair of + * REVOKE - INSERT updates represent a monotone change pair. The mailbox is used by {@link PosetAwareReceiver}s. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class PosetAwareMailbox extends AbstractUpdateSplittingMailbox { + + protected final TupleMask groupMask; + + public PosetAwareMailbox(final PosetAwareReceiver receiver, final ReteContainer container) { + super(receiver, container, () -> new GroupBasedMessageIndexer(receiver.getCoreMask())); + this.groupMask = receiver.getCoreMask(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0; + final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0; + final TupleMask coreMask = this.receiver.getCoreMask(); + + // it cannot happen that it was present in both + assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone); + + if (direction == Direction.INSERT) { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.insert(update); + } else { + // it was a monotone one before or did not exist at all + monotoneQueue.insert(update); + + // if it was not present in the monotone queue before, then + // we need to check whether it makes REVOKE updates monotone + if (!wasPresentAsMonotone) { + final Set counterParts = tryFindCounterPart(update, false, true); + for (final Tuple counterPart : counterParts) { + final int count = antiMonotoneQueue.getCount(counterPart); + assert count < 0; + antiMonotoneQueue.update(counterPart, -count); + monotoneQueue.update(counterPart, count); + } + } + } + } else { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.delete(update); + } else if (wasPresentAsMonotone) { + // it was a monotone one before + monotoneQueue.delete(update); + + // and we need to check whether the monotone REVOKE updates + // still have a reinforcing counterpart + final Set candidates = new HashSet(); + final Tuple key = coreMask.transform(update); + for (final Entry entry : monotoneQueue.getTuplesByGroup(key).entrySet()) { + if (entry.getValue() < 0) { + final Tuple candidate = entry.getKey(); + final Set counterParts = tryFindCounterPart(candidate, true, false); + if (counterParts.isEmpty()) { + // all of them are gone + candidates.add(candidate); + } + } + } + + // move the candidates from the monotone queue to the + // anti-monotone queue because they do not have a + // counterpart anymore + for (final Tuple candidate : candidates) { + final int count = monotoneQueue.getCount(candidate); + assert count < 0; + monotoneQueue.update(candidate, -count); + antiMonotoneQueue.update(candidate, count); + } + } else { + // it did not exist before + final Set counterParts = tryFindCounterPart(update, true, false); + if (counterParts.isEmpty()) { + // there is no tuple that would make this update monotone + antiMonotoneQueue.delete(update); + } else { + // there is a reinforcing counterpart + monotoneQueue.delete(update); + } + } + } + + if (antiMonotoneQueue.isEmpty()) { + this.group.notifyLostAllMessages(this, PhasedSelector.ANTI_MONOTONE); + } else { + this.group.notifyHasMessage(this, PhasedSelector.ANTI_MONOTONE); + } + + if (monotoneQueue.isEmpty()) { + this.group.notifyLostAllMessages(this, PhasedSelector.MONOTONE); + } else { + this.group.notifyHasMessage(this, PhasedSelector.MONOTONE); + } + } + + protected Set tryFindCounterPart(final Tuple first, final boolean findPositiveCounterPart, + final boolean findAllCounterParts) { + final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final TupleMask coreMask = this.receiver.getCoreMask(); + final TupleMask posetMask = this.receiver.getPosetMask(); + final IPosetComparator posetComparator = this.receiver.getPosetComparator(); + final Set result = CollectionsFactory.createSet(); + final Tuple firstKey = coreMask.transform(first); + final Tuple firstValue = posetMask.transform(first); + + if (findPositiveCounterPart) { + for (final Entry entry : monotoneQueue.getTuplesByGroup(firstKey).entrySet()) { + final Tuple secondValue = posetMask.transform(entry.getKey()); + if (entry.getValue() > 0 && posetComparator.isLessOrEqual(firstValue, secondValue)) { + result.add(entry.getKey()); + if (!findAllCounterParts) { + return result; + } + } + } + } else { + for (final Entry entry : antiMonotoneQueue.getTuplesByGroup(firstKey).entrySet()) { + final Tuple secondValue = posetMask.transform(entry.getKey()); + if (posetComparator.isLessOrEqual(secondValue, firstValue)) { + result.add(entry.getKey()); + if (!findAllCounterParts) { + return result; + } + } + } + } + + return result; + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.deliveringAntiMonotone = true; + + for (final Tuple group : this.antiMonotoneQueue.getGroups()) { + for (final Entry entry : this.antiMonotoneQueue.getTuplesByGroup(group).entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count < 0; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.updateWithPosetInfo(Direction.DELETE, update, false); + } + } + } + + this.deliveringAntiMonotone = false; + swapAndClearAntiMonotone(); + } else if (kind == PhasedSelector.MONOTONE) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.deliveringMonotone = true; + + for (final Tuple group : this.monotoneQueue.getGroups()) { + for (final Entry entry : this.monotoneQueue.getTuplesByGroup(group).entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count != 0; + final Direction direction = count < 0 ? Direction.DELETE : Direction.INSERT; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.updateWithPosetInfo(direction, update, true); + } + } + } + + this.deliveringMonotone = false; + swapAndClearMonotone(); + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "PA_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " " + + this.getActiveAntiMonotoneQueue(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.indexer.DefaultMessageIndexer; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A mailbox implementation that splits updates messages according to the standard subset ordering into anti-monotonic + * (deletions) and monotonic (insertions) updates. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class UpdateSplittingMailbox extends AbstractUpdateSplittingMailbox + implements AdaptableMailbox { + + protected Mailbox adapter; + + public UpdateSplittingMailbox(final Receiver receiver, final ReteContainer container) { + super(receiver, container, DefaultMessageIndexer::new); + this.adapter = this; + } + + @Override + public Mailbox getAdapter() { + return this.adapter; + } + + @Override + public void setAdapter(final Mailbox adapter) { + this.adapter = adapter; + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final DefaultMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final DefaultMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0; + final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0; + + // it cannot happen that it was present in both + assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone); + + if (direction == Direction.INSERT) { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.insert(update); + } else { + // it was a monotone one before or did not exist at all + monotoneQueue.insert(update); + } + } else { + if (wasPresentAsMonotone) { + // it was a monotone one before + monotoneQueue.delete(update); + } else { + // it was an anti-monotone one before or did not exist at all + antiMonotoneQueue.delete(update); + } + } + + final Mailbox targetMailbox = this.adapter; + final CommunicationGroup targetGroup = this.adapter.getCurrentGroup(); + + if (antiMonotoneQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.ANTI_MONOTONE); + } else { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.ANTI_MONOTONE); + } + + if (monotoneQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.MONOTONE); + } else { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.MONOTONE); + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + // deliver anti-monotone + this.deliveringAntiMonotone = true; + for (final Entry entry : this.antiMonotoneQueue.getTuples().entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count < 0; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.update(Direction.DELETE, update, Timestamp.ZERO); + } + } + this.deliveringAntiMonotone = false; + swapAndClearAntiMonotone(); + } else if (kind == PhasedSelector.MONOTONE) { + // deliver monotone + this.deliveringMonotone = true; + for (final Entry entry : this.monotoneQueue.getTuples().entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count > 0; + for (int i = 0; i < count; i++) { + this.receiver.update(Direction.INSERT, update, Timestamp.ZERO); + } + } + this.deliveringMonotone = false; + swapAndClearMonotone(); + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "US_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " " + + this.getActiveAntiMonotoneQueue(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timely; + +import java.util.Map; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +public class TimelyMailbox implements Mailbox { + + protected TreeMap> queue; + protected final Receiver receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + protected boolean fallThrough; + + public TimelyMailbox(final Receiver receiver, final ReteContainer container) { + this.receiver = receiver; + this.container = container; + this.queue = CollectionsFactory.createTreeMap(); + } + + protected TreeMap> getActiveQueue() { + return this.queue; + } + + @Override + public boolean isEmpty() { + return getActiveQueue().isEmpty(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final TreeMap> activeQueue = getActiveQueue(); + + Map tupleMap = activeQueue.get(timestamp); + final boolean wasEmpty = tupleMap == null; + boolean significantChange = false; + + if (tupleMap == null) { + tupleMap = CollectionsFactory.createMap(); + activeQueue.put(timestamp, tupleMap); + significantChange = true; + } + + Integer count = tupleMap.get(update); + if (count == null) { + count = 0; + significantChange = true; + } + + if (direction == Direction.DELETE) { + count--; + } else { + count++; + } + + if (count == 0) { + tupleMap.remove(update); + if (tupleMap.isEmpty()) { + activeQueue.remove(timestamp); + } + significantChange = true; + } else { + tupleMap.put(update, count); + } + + if (significantChange) { + if (wasEmpty) { + this.group.notifyHasMessage(this, timestamp); + } else if (tupleMap.isEmpty()) { + final Timestamp resumableTimestamp = (this.receiver instanceof ResumableNode) + ? ((ResumableNode) this.receiver).getResumableTimestamp() + : null; + // check if there is folding left to do before unsubscribing just based on the message queue being empty + if (resumableTimestamp == null || resumableTimestamp.compareTo(timestamp) != 0) { + this.group.notifyLostAllMessages(this, timestamp); + } + } + } + } + + @Override + public void deliverAll(final MessageSelector selector) { + if (selector instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) selector; + // REMOVE the tuples associated with the selector, dont just query them + final Map tupleMap = this.queue.remove(timestamp); + + // tupleMap may be empty if we only have lazy folding to do + if (tupleMap != null) { + this.receiver.batchUpdate(tupleMap.entrySet(), timestamp); + } + + if (this.container.getTimelyConfiguration() + .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) { + // (1) either normal delivery, which ended up being a lazy folding state + // (2) and/or lazy folding needs to be resumed + if (this.receiver instanceof ResumableNode) { + ((ResumableNode) this.receiver).resumeAt(timestamp); + } + } + } else { + throw new IllegalArgumentException("Unsupported message selector " + selector); + } + } + + @Override + public String toString() { + return "DDF_MBOX (" + this.receiver + ") " + this.getActiveQueue(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.queue.clear(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Remote identifier of a node of type T. + * + * @author Gabor Bergmann + * + */ +public class Address { + ReteContainer container; + Long nodeId; + /** + * Feel free to leave null e.g. if node is in a separate JVM. + */ + T nodeCache; + + /** + * Address of local node (use only for containers in the same VM!) + */ + public static Address of(N node) { + return new Address(node); + } + + /** + * General constructor. + * + * @param container + * @param nodeId + */ + public Address(ReteContainer container, Long nodeId) { + super(); + this.container = container; + this.nodeId = nodeId; + } + + /** + * Local-only constructor. (use only for containers in the same VM!) + * + * @param node + * the node to address + */ + public Address(T node) { + super(); + this.nodeCache = node; + this.container = node.getContainer(); + this.nodeId = node.getNodeId(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((container == null) ? 0 : container.hashCode()); + result = prime * result + ((nodeId == null) ? 0 : nodeId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Address)) + return false; + final Address other = (Address) obj; + if (container == null) { + if (other.container != null) + return false; + } else if (!container.equals(other.container)) + return false; + if (nodeId == null) { + if (other.nodeId != null) + return false; + } else if (!nodeId.equals(other.nodeId)) + return false; + return true; + } + + public ReteContainer getContainer() { + return container; + } + + public void setContainer(ReteContainer container) { + this.container = container; + } + + public Long getNodeId() { + return nodeId; + } + + public void setNodeId(Long nodeId) { + this.nodeId = nodeId; + } + + public T getNodeCache() { + return nodeCache; + } + + public void setNodeCache(T nodeCache) { + this.nodeCache = nodeCache; + } + + @Override + public String toString() { + if (nodeCache == null) + return "A(" + nodeId + " @ " + container + ")"; + else + return "A(" + nodeCache + ")"; + + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * This node delivers updates to a remote recipient; no updates are propagated further in this network. + * + * @author Gabor Bergmann + * + */ +public class RemoteReceiver extends SingleInputNode { + + List> targets; + + public RemoteReceiver(ReteContainer reteContainer) { + super(reteContainer); + targets = new ArrayList>(); + } + + public void addTarget(Address target) { + targets.add(target); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + throw new UnsupportedOperationException(); + } + + public Collection remotePull(boolean flush) { + return reteContainer.pullContents(this, flush); + } + + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + for (Address ad : targets) + reteContainer.sendUpdateToRemoteAddress(ad, direction, updateElement); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * This node receives updates from a remote supplier; no local updates are expected. + * + * @author Gabor Bergmann + * + */ +public class RemoteSupplier extends SingleInputNode { + + RemoteReceiver counterpart; + + public RemoteSupplier(ReteContainer reteContainer, RemoteReceiver counterpart) { + super(reteContainer); + this.counterpart = counterpart; + counterpart.addTarget(reteContainer.makeAddress(this)); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + Collection pulled = counterpart.remotePull(flush); + collector.addAll(pulled); + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.Tunnel; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * Ensures that no identical copies get to the output. Only one replica of each pattern substitution may traverse this + * node. There are both timeless and timely implementations. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + * @since 2.2 + */ +public abstract class AbstractUniquenessEnforcerNode extends StandardNode implements Tunnel { + + protected final Collection parents; + protected ProjectionIndexer memoryNullIndexer; + protected ProjectionIndexer memoryIdentityIndexer; + protected final int tupleWidth; + // MUST BE INSTANTIATED IN THE CONCRETE SUBCLASSES AFTER ALL FIELDS ARE SET + protected Mailbox mailbox; + protected final TupleMask nullMask; + protected final TupleMask identityMask; + protected final List specializedListeners; + + public AbstractUniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) { + super(reteContainer); + this.parents = new ArrayList(); + this.specializedListeners = new ArrayList(); + this.tupleWidth = tupleWidth; + this.nullMask = TupleMask.linear(0, tupleWidth); + this.identityMask = TupleMask.identity(tupleWidth); + } + + protected abstract Mailbox instantiateMailbox(); + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + /** + * @since 2.8 + */ + public abstract Set getTuples(); + + /** + * @since 2.4 + */ + protected void propagate(final Direction direction, final Tuple update, final Timestamp timestamp) { + // See Bug 518434 + // trivial (non-active) indexers must be updated before other listeners + // so that if they are joined against each other, trivial indexers lookups + // will be consistent with their notifications; + // also, their subscriptions must share a single order + for (final ListenerSubscription subscription : specializedListeners) { + subscription.propagate(direction, update, timestamp); + } + propagateUpdate(direction, update, timestamp); + } + + @Override + public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) { + if (Options.employTrivialIndexers) { + if (nullMask.equals(mask)) { + final ProjectionIndexer indexer = getNullIndexer(); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + return indexer; + } + if (identityMask.equals(mask)) { + final ProjectionIndexer indexer = getIdentityIndexer(); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + return indexer; + } + } + return super.constructIndex(mask, traces); + } + + public abstract ProjectionIndexer getNullIndexer(); + + public abstract ProjectionIndexer getIdentityIndexer(); + + @Override + public void appendParent(final Supplier supplier) { + parents.add(supplier); + } + + @Override + public void removeParent(final Supplier supplier) { + parents.remove(supplier); + } + + @Override + public Collection getParents() { + return parents; + } + + @Override + public void assignTraceInfo(final TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) { + for (final Supplier parent : parents) { + parent.acceptPropagatedTraceInfo(traceInfo); + } + } + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Bergmann Gabor + * + */ +public class CallbackNode extends SimpleReceiver { + + IUpdateable updateable; + + public CallbackNode(ReteContainer reteContainer, IUpdateable updateable) + { + super(reteContainer); + this.updateable = updateable; + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + updateable.update(updateElement, direction == Direction.INSERT); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Default implementation of the Production node, based on UniquenessEnforcerNode + * + * @author Gabor Bergmann + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class DefaultProductionNode extends UniquenessEnforcerNode implements ProductionNode { + + protected final Map posMapping; + + /** + * @since 1.6 + */ + public DefaultProductionNode(final ReteContainer reteContainer, final Map posMapping, + final boolean deleteRederiveEvaluation) { + this(reteContainer, posMapping, deleteRederiveEvaluation, null, null, null); + } + + /** + * @since 1.6 + */ + public DefaultProductionNode(final ReteContainer reteContainer, final Map posMapping, + final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask, + final IPosetComparator posetComparator) { + super(reteContainer, posMapping.size(), deleteRederiveEvaluation, coreMask, posetMask, posetComparator); + this.posMapping = posMapping; + } + + @Override + public Map getPosMapping() { + return posMapping; + } + + @Override + public Iterator iterator() { + return memory.iterator(); + } + + @Override + public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) { + if (traceInfo.propagateToProductionNodeParentAlso()) { + super.acceptPropagatedTraceInfo(traceInfo); + } + } + + @Override + public String toString() { + for (final TraceInfo traceInfo : this.traceInfos) { + if (traceInfo instanceof CompiledQuery) { + final String patternName = ((CompiledQuery) traceInfo).getPatternName(); + return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString()); + } + } + return super.toString(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A bucket holds a filtered set of tuples of its parent {@link DiscriminatorDispatcherNode}. + * Exactly those that have the given bucket key at their discrimination column. + * + *

During operation, tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + * + * @author Gabor Bergmann + * @since 1.5 + */ +public class DiscriminatorBucketNode extends SingleInputNode { + + private Object bucketKey; + + /** + * @param bucketKey will be wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + + */ + public DiscriminatorBucketNode(ReteContainer reteContainer, Object bucketKey) { + super(reteContainer); + this.bucketKey = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapElement(bucketKey); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (parent != null) { + getDispatcher().pullIntoFiltered(collector, bucketKey, flush); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (parent != null) { + getDispatcher().pullIntoWithTimestampFiltered(collector, bucketKey, flush); + } + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + + public Object getBucketKey() { + return bucketKey; + } + + @Override + public void appendParent(Supplier supplier) { + if (! (supplier instanceof DiscriminatorDispatcherNode)) + throw new IllegalArgumentException(); + super.appendParent(supplier); + } + + public DiscriminatorDispatcherNode getDispatcher() { + return (DiscriminatorDispatcherNode) parent; + } + + @Override + protected String toStringCore() { + return String.format("%s<%s=='%s'>", + super.toStringCore(), + (getDispatcher() == null) ? "?" : getDispatcher().getDiscriminationColumnIndex(), + bucketKey); + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Node that sends tuples off to different buckets (attached as children of type {@link DiscriminatorBucketNode}), based + * on the value of a given column. + * + *

+ * Tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + * + * @author Gabor Bergmann + * @since 1.5 + */ +public class DiscriminatorDispatcherNode extends SingleInputNode implements NetworkStructureChangeSensitiveNode { + + private int discriminationColumnIndex; + private Map buckets = new HashMap<>(); + private Map bucketMailboxes = new HashMap<>(); + + /** + * @param reteContainer + */ + public DiscriminatorDispatcherNode(ReteContainer reteContainer, int discriminationColumnIndex) { + super(reteContainer); + this.discriminationColumnIndex = discriminationColumnIndex; + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + Object dispatchKey = updateElement.get(discriminationColumnIndex); + Mailbox bucketMailBox = bucketMailboxes.get(dispatchKey); + if (bucketMailBox != null) { + bucketMailBox.postMessage(direction, updateElement, timestamp); + } + } + + public int getDiscriminationColumnIndex() { + return discriminationColumnIndex; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + propagatePullIntoWithTimestamp(collector, flush); + } + + /** + * @since 2.3 + */ + public void pullIntoFiltered(final Collection collector, final Object bucketKey, final boolean flush) { + final ArrayList unfiltered = new ArrayList(); + propagatePullInto(unfiltered, flush); + for (Tuple tuple : unfiltered) { + if (bucketKey.equals(tuple.get(discriminationColumnIndex))) { + collector.add(tuple); + } + } + } + + /** + * @since 2.3 + */ + public void pullIntoWithTimestampFiltered(final Map> collector, final Object bucketKey, + final boolean flush) { + final Map> unfiltered = CollectionsFactory.createMap(); + propagatePullIntoWithTimestamp(unfiltered, flush); + for (final Entry> entry : unfiltered.entrySet()) { + if (bucketKey.equals(entry.getKey().get(discriminationColumnIndex))) { + collector.put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public void appendChild(Receiver receiver) { + super.appendChild(receiver); + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + DiscriminatorBucketNode old = buckets.put(bucketKey, bucket); + if (old != null) { + throw new IllegalStateException(); + } + bucketMailboxes.put(bucketKey, this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox())); + } + } + + /** + * @since 2.2 + */ + public Map getBucketMailboxes() { + return this.bucketMailboxes; + } + + @Override + public void networkStructureChanged() { + bucketMailboxes.clear(); + for (Receiver receiver : children) { + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + bucketMailboxes.put(bucketKey, + this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox())); + } + } + } + + @Override + public void removeChild(Receiver receiver) { + super.removeChild(receiver); + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + DiscriminatorBucketNode old = buckets.remove(bucketKey); + if (old != bucket) + throw new IllegalStateException(); + bucketMailboxes.remove(bucketKey); + } + } + + @Override + protected String toStringCore() { + return super.toStringCore() + '<' + discriminationColumnIndex + '>'; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +public class EqualityFilterNode extends FilterNode { + + int[] indices; + int first; + + /** + * @param reteContainer + * @param indices + * indices of the Tuple that should hold equal values + */ + public EqualityFilterNode(ReteContainer reteContainer, int[] indices) { + super(reteContainer); + this.indices = indices; + first = indices[0]; + } + + @Override + public boolean check(Tuple ps) { + Object firstElement = ps.get(first); + for (int i = 1 /* first is omitted */; i < indices.length; i++) { + if (!ps.get(indices[i]).equals(firstElement)) + return false; + } + return true; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * This node implements a simple filter. A stateless abstract check() predicate determines whether a matching is allowed + * to pass. + * + * @author Gabor Bergmann + * + */ +public abstract class FilterNode extends SingleInputNode { + + public FilterNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * Abstract filtering predicate. Expected to be stateless. + * + * @param ps + * the matching to be checked. + * @return true if and only if the parameter matching is allowed to pass through this node. + */ + public abstract boolean check(final Tuple ps); + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple ps : this.reteContainer.pullPropagatedContents(this, flush)) { + if (check(ps)) { + collector.add(ps); + } + } + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + for (final Entry> entry : this.reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) { + if (check(entry.getKey())) { + collector.put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + if (check(updateElement)) { + propagateUpdate(direction, updateElement, timestamp); + } + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * This node filters patterns according to equalities and inequalities of elements. The 'subject' element is asserted to + * be different from the elements given by the inequalityMask. + * + * + * @author Gabor Bergmann + * + */ +public class InequalityFilterNode extends FilterNode { + + int subjectIndex; + TupleMask inequalityMask; + + /** + * @param reteContainer + * @param subject + * the index of the element that should be compared. + * @param inequalityMask + * the indices of elements that should be different from the subjectIndex. + */ + public InequalityFilterNode(ReteContainer reteContainer, int subject, TupleMask inequalityMask) { + super(reteContainer); + this.subjectIndex = subject; + this.inequalityMask = inequalityMask; + } + + @Override + public boolean check(Tuple ps) { + Object subject = ps.get(subjectIndex); + for (int ineq : inequalityMask.indices) { + if (subject.equals(ps.get(ineq))) + return false; + } + return true; + } + +} 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..6a1e305c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * Copyright (c) 2023 The Refinery Authors + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeObserver; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReinitializedNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +import java.util.Collection; +import java.util.Map; + +public class RepresentativeElectionNode extends SingleInputNode implements Clearable, RepresentativeObserver, + ReinitializedNode { + private final RepresentativeElectionAlgorithm.Factory algorithmFactory; + private Graph graph; + private RepresentativeElectionAlgorithm algorithm; + + public RepresentativeElectionNode(ReteContainer reteContainer, + RepresentativeElectionAlgorithm.Factory algorithmFactory) { + super(reteContainer); + this.algorithmFactory = algorithmFactory; + graph = new Graph<>(); + algorithm = algorithmFactory.create(graph); + algorithm.setObserver(this); + reteContainer.registerClearable(this); + } + + @Override + public void networkStructureChanged() { + if (reteContainer.isTimelyEvaluation() && reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + @Override + public void reinitializeWith(Collection tuples) { + algorithm.dispose(); + graph = new Graph<>(); + for (var tuple : tuples) { + insertEdge(tuple.get(0), tuple.get(1)); + } + algorithm = algorithmFactory.create(graph); + algorithm.setObserver(this); + } + + @Override + public void tupleChanged(Object source, Object representative, Direction direction) { + var tuple = Tuples.staticArityFlatTupleOf(source, representative); + propagateUpdate(direction, tuple, Timestamp.ZERO); + } + + @Override + public void clear() { + algorithm.dispose(); + graph = new Graph<>(); + algorithm = algorithmFactory.create(graph); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + var source = updateElement.get(0); + var target = updateElement.get(1); + switch (direction) { + case INSERT -> insertEdge(source, target); + case DELETE -> deleteEdge(source, target); + default -> throw new IllegalArgumentException("Unknown direction: " + direction); + } + } + + private void insertEdge(Object source, Object target) { + graph.insertNode(source); + graph.insertNode(target); + graph.insertEdge(source, target); + } + + private void deleteEdge(Object source, Object target) { + graph.deleteEdgeIfExists(source, target); + if (isIsolated(source)) { + graph.deleteNode(source); + } + if (!source.equals(target) && isIsolated(target)) { + graph.deleteNode(target); + } + } + + private boolean isIsolated(Object node) { + return graph.getTargetNodes(node).isEmpty() && graph.getSourceNodes(node).isEmpty(); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + for (var entry : algorithm.getComponents().entrySet()) { + var representative = entry.getKey(); + for (var node : entry.getValue()) { + collector.add(Tuples.staticArityFlatTupleOf(node, representative)); + } + } + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + // Use all zero timestamps because this node cannot be used in recursive groups anyway. + for (var entry : algorithm.getComponents().entrySet()) { + var representative = entry.getKey(); + for (var node : entry.getValue()) { + collector.put(Tuples.staticArityFlatTupleOf(node, representative), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + } +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.Tunnel; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Gabor Bergmann + * + */ +public abstract class SingleInputNode extends StandardNode implements Tunnel { + + protected Supplier parent; + /** + * @since 1.6 + */ + protected Mailbox mailbox; + + public SingleInputNode(ReteContainer reteContainer) { + super(reteContainer); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + parent = null; + } + + /** + * Instantiates the {@link Mailbox} of this receiver. + * Subclasses may override this method to provide their own mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public CommunicationTracker getCommunicationTracker() { + return this.reteContainer.getCommunicationTracker(); + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + @Override + public void appendParent(Supplier supplier) { + if (parent == null) + parent = supplier; + else + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + + ") as it is not a Uniqueness Enforcer Node. "); + } + + @Override + public void removeParent(Supplier supplier) { + if (parent == supplier) + parent = null; + else + throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not " + + supplier); + } + + /** + * To be called by derived classes and ReteContainer. + */ + public void propagatePullInto(final Collection collector, final boolean flush) { + if (parent != null) { + parent.pullInto(collector, flush); + } + } + + /** + * To be called by derived classes and ReteContainer. + */ + public void propagatePullIntoWithTimestamp(final Map> collector, final boolean flush) { + if (parent != null) { + parent.pullIntoWithTimeline(collector, flush); + } + } + + @Override + public Collection getParents() { + if (parent == null) + return Collections.emptySet(); + else + return Collections.singleton(parent); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +/** + * Differential dataflow implementation of the Production node, based on {@link TimelyUniquenessEnforcerNode}. + * + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @since 2.3 + */ +public class TimelyProductionNode extends TimelyUniquenessEnforcerNode implements ProductionNode { + + protected final Map posMapping; + + public TimelyProductionNode(final ReteContainer reteContainer, final Map posMapping) { + super(reteContainer, posMapping.size()); + this.posMapping = posMapping; + } + + @Override + public Map getPosMapping() { + return this.posMapping; + } + + @Override + public Iterator iterator() { + return this.memory.keySet().iterator(); + } + + @Override + public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) { + if (traceInfo.propagateToProductionNodeParentAlso()) { + super.acceptPropagatedTraceInfo(traceInfo); + } + } + + @Override + public String toString() { + for (final TraceInfo traceInfo : this.traceInfos) { + if (traceInfo instanceof CompiledQuery) { + final String patternName = ((CompiledQuery) traceInfo).getPatternName(); + return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString()); + } + } + return super.toString(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryIdentityIndexer; +import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryNullIndexer; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * Timely uniqueness enforcer node implementation. + * + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + * @since 2.4 + */ +public class TimelyUniquenessEnforcerNode extends AbstractUniquenessEnforcerNode implements ResumableNode { + + protected final TimelyMemory memory; + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + public TimelyUniquenessEnforcerNode(final ReteContainer container, final int tupleWidth) { + super(container, tupleWidth); + this.memory = new TimelyMemory( + container.getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + container.registerClearable(this.memory); + this.mailbox = instantiateMailbox(); + container.registerClearable(this.mailbox); + } + + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : this.memory.getTuplesAtInfinity()) { + collector.add(tuple); + } + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public Set getTuples() { + return this.memory.getTuplesAtInfinity(); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + return this.memory.getResumableTimestamp(); + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + final Map> diffMap = this.memory.resumeAt(timestamp); + for (final Entry> entry : diffMap.entrySet()) { + for (final Signed signed : entry.getValue()) { + propagate(signed.getDirection(), entry.getKey(), signed.getPayload()); + } + } + final Timestamp nextTimestamp = this.memory.getResumableTimestamp(); + if (nextTimestamp != null) { + this.group.notifyHasMessage(this.mailbox, nextTimestamp); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + Diff resultDiff = null; + if (direction == Direction.INSERT) { + resultDiff = this.memory.put(update, timestamp); + } else { + try { + resultDiff = this.memory.remove(update, timestamp); + } catch (final IllegalStateException e) { + issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in " + + this.getClass().getName() + " " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), e); + // diff will remain unset in case of the exception, it is time to return + return; + } + } + + for (final Signed signed : resultDiff) { + propagate(signed.getDirection(), update, signed.getPayload()); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + collector.putAll(this.memory.asMap()); + } + + @Override + public ProjectionIndexer getNullIndexer() { + if (this.memoryNullIndexer == null) { + this.memoryNullIndexer = new TimelyMemoryNullIndexer(this.reteContainer, this.tupleWidth, this.memory, this, + this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer); + } + return this.memoryNullIndexer; + } + + @Override + public ProjectionIndexer getIdentityIndexer() { + if (this.memoryIdentityIndexer == null) { + this.memoryIdentityIndexer = new TimelyMemoryIdentityIndexer(this.reteContainer, this.tupleWidth, + this.memory, this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer); + } + return this.memoryIdentityIndexer; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public abstract class TransformerNode extends SingleInputNode { + + public TransformerNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + protected abstract Tuple transform(final Tuple input); + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (Tuple ps : reteContainer.pullPropagatedContents(this, flush)) { + collector.add(transform(ps)); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + for (final Entry> entry : reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) { + collector.put(transform(entry.getKey()), entry.getValue()); + } + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + propagateUpdate(direction, transform(updateElement), timestamp); + } + +} 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..eeead31b --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.ReinitializedNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +import java.util.Collection; +import java.util.Map; + +/** + * This class represents a transitive closure node in the Rete net. + *

+ * This node must not be used in recursive {@link CommunicationGroup}s. + * + * @author Gabor Bergmann + * + */ +public class TransitiveClosureNode extends SingleInputNode + implements Clearable, ITcObserver, NetworkStructureChangeSensitiveNode, ReinitializedNode { + + private Graph graphDataSource; + private ITcDataSource transitiveClosureAlgorithm; + + /** + * Create a new transitive closure rete node. + * + * Client may optionally call {@link #reinitializeWith(Collection)} before using the node, instead of inserting the + * initial set of tuples one by one. + * + * @param reteContainer + * the rete container of the node + */ + public TransitiveClosureNode(ReteContainer reteContainer) { + super(reteContainer); + graphDataSource = new Graph(); + transitiveClosureAlgorithm = new IncSCCAlg(graphDataSource); + transitiveClosureAlgorithm.attachObserver(this); + reteContainer.registerClearable(this); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + /** + * Initializes the graph data source with the given collection of tuples. + * + * @param tuples + * the initial collection of tuples + */ + @Override + public void reinitializeWith(Collection tuples) { + clear(); + + for (tools.refinery.viatra.runtime.matchers.tuple.Tuple t : tuples) { + graphDataSource.insertNode(t.get(0)); + graphDataSource.insertNode(t.get(1)); + graphDataSource.insertEdge(t.get(0), t.get(1)); + } + transitiveClosureAlgorithm.attachObserver(this); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : ((IncSCCAlg) transitiveClosureAlgorithm).getTcRelation()) { + collector.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget())); + } + } + + @Override + public void pullIntoWithTimeline( + final Map> collector, + final boolean flush) { + // use all zero timestamps because this node cannot be used in recursive groups anyway + for (final Tuple tuple : ((IncSCCAlg) transitiveClosureAlgorithm).getTcRelation()) { + collector.put(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget()), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + + @Override + public void update(Direction direction, tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, + Timestamp timestamp) { + if (updateElement.getSize() == 2) { + Object source = updateElement.get(0); + Object target = updateElement.get(1); + + if (direction == Direction.INSERT) { + graphDataSource.insertNode(source); + graphDataSource.insertNode(target); + graphDataSource.insertEdge(source, target); + } + if (direction == Direction.DELETE) { + graphDataSource.deleteEdgeIfExists(source, target); + + if (((IncSCCAlg) transitiveClosureAlgorithm).isIsolated(source)) { + graphDataSource.deleteNode(source); + } + if (!source.equals(target) && ((IncSCCAlg) transitiveClosureAlgorithm).isIsolated(target)) { + graphDataSource.deleteNode(target); + } + } + } + } + + @Override + public void clear() { + transitiveClosureAlgorithm.dispose(); + graphDataSource = new Graph(); + transitiveClosureAlgorithm = new IncSCCAlg(graphDataSource); + } + + @Override + public void tupleInserted(Object source, Object target) { + tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO); + } + + @Override + public void tupleDeleted(Object source, Object target) { + tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Simply propagates everything. Might be used to join or fork. + * + * @author Gabor Bergmann + */ +public class TransparentNode extends SingleInputNode { + + public TransparentNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + propagatePullIntoWithTimestamp(collector, flush); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Trims the matchings as specified by a mask. + * + * @author Gabor Bergmann + * + */ +public class TrimmerNode extends TransformerNode { + + protected TupleMask mask; + + /** + * @param reteContainer + * @param mask + * The mask used to trim substitutions. + */ + public TrimmerNode(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.mask = mask; + } + + public TrimmerNode(ReteContainer reteContainer) { + super(reteContainer); + this.mask = null; + } + + /** + * @return the mask + */ + public TupleMask getMask() { + return mask; + } + + /** + * @param mask + * the mask to set + */ + public void setMask(TupleMask mask) { + this.mask = mask; + } + + @Override + protected Tuple transform(Tuple input) { + return mask.transform(input); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann, Tamas Szabo and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.MemoryIdentityIndexer; +import tools.refinery.viatra.runtime.rete.index.MemoryNullIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox; + +/** + * Timeless uniqueness enforcer node implementation. + *

+ * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the + * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached + * during the evaluation. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + */ +public class UniquenessEnforcerNode extends AbstractUniquenessEnforcerNode + implements RederivableNode, PosetAwareReceiver { + + protected IMultiset memory; + /** + * @since 1.6 + */ + protected IMultiset rederivableMemory; + /** + * @since 1.6 + */ + protected boolean deleteRederiveEvaluation; + + /** + * @since 1.7 + */ + protected CommunicationGroup currentGroup; + + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) { + this(reteContainer, tupleWidth, false); + } + + /** + * OPTIONAL ELEMENT - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final TupleMask coreMask; + /** + * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final TupleMask posetMask; + /** + * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final IPosetComparator posetComparator; + + /** + * @since 1.6 + */ + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth, + final boolean deleteRederiveEvaluation) { + this(reteContainer, tupleWidth, deleteRederiveEvaluation, null, null, null); + } + + /** + * @since 1.6 + */ + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth, + final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask, + final IPosetComparator posetComparator) { + super(reteContainer, tupleWidth); + this.memory = CollectionsFactory.createMultiset(); + this.rederivableMemory = CollectionsFactory.createMultiset(); + reteContainer.registerClearable(this.memory); + reteContainer.registerClearable(this.rederivableMemory); + this.deleteRederiveEvaluation = deleteRederiveEvaluation; + this.coreMask = coreMask; + this.posetMask = posetMask; + this.posetComparator = posetComparator; + this.mailbox = instantiateMailbox(); + reteContainer.registerClearable(this.mailbox); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : this.memory.distinctValues()) { + collector.add(tuple); + } + } + + /** + * @since 2.8 + */ + @Override + public Set getTuples() { + return this.memory.distinctValues(); + } + + @Override + public boolean isInDRedMode() { + return this.deleteRederiveEvaluation; + } + + @Override + public TupleMask getCoreMask() { + return coreMask; + } + + @Override + public TupleMask getPosetMask() { + return posetMask; + } + + @Override + public IPosetComparator getPosetComparator() { + return posetComparator; + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException("Use the timely version of this node!"); + } + + /** + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (coreMask != null && posetMask != null && posetComparator != null) { + return new PosetAwareMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + updateWithPosetInfo(direction, update, false); + } + + @Override + public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) { + if (this.deleteRederiveEvaluation) { + if (updateWithDeleteAndRederive(direction, update, monotone)) { + propagate(direction, update, Timestamp.ZERO); + } + } else { + if (updateDefault(direction, update)) { + propagate(direction, update, Timestamp.ZERO); + } + } + } + + /** + * @since 2.4 + */ + protected boolean updateWithDeleteAndRederive(final Direction direction, final Tuple update, + final boolean monotone) { + boolean propagate = false; + + final int memoryCount = memory.getCount(update); + final int rederivableCount = rederivableMemory.getCount(update); + + if (direction == Direction.INSERT) { + // INSERT + if (rederivableCount != 0) { + // the tuple is in the re-derivable memory + rederivableMemory.addOne(update); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the INSERT cancelled out a DELETE + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the tuple is in the main memory + propagate = memory.addOne(update); + } + } else { + // DELETE + if (rederivableCount != 0) { + // the tuple is in the re-derivable memory + if (memoryCount != 0) { + issueError("[INTERNAL ERROR] Inconsistent state for " + update + + " because it is present both in the main and re-derivable memory in the UniquenessEnforcerNode " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null); + } + + try { + rederivableMemory.removeOne(update); + } catch (final IllegalStateException ex) { + issueError( + "[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in UniquenessEnforcer " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), + ex); + } + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the tuple is in the main memory + if (monotone) { + propagate = memory.removeOne(update); + } else { + final int count = memoryCount - 1; + if (count > 0) { + if (rederivableMemory.isEmpty()) { + // there is now something to be re-derived + ((RecursiveCommunicationGroup) currentGroup).addRederivable(this); + } + rederivableMemory.addPositive(update, count); + } + memory.clearAllOf(update); + propagate = true; + } + } + } + + return propagate; + } + + /** + * @since 2.4 + */ + protected boolean updateDefault(final Direction direction, final Tuple update) { + boolean propagate = false; + if (direction == Direction.INSERT) { + // INSERT + propagate = memory.addOne(update); + } else { + // DELETE + try { + propagate = memory.removeOne(update); + } catch (final IllegalStateException ex) { + propagate = false; + issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in " + + this.getClass().getName() + " " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), ex); + } + } + return propagate; + } + + /** + * @since 1.6 + */ + @Override + public void rederiveOne() { + final Tuple update = rederivableMemory.iterator().next(); + final int count = rederivableMemory.getCount(update); + rederivableMemory.clearAllOf(update); + memory.addPositive(update, count); + // if there is no other re-derivable tuple, then unregister the node itself + if (this.rederivableMemory.isEmpty()) { + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + propagate(Direction.INSERT, update, Timestamp.ZERO); + } + + @Override + public ProjectionIndexer getNullIndexer() { + if (this.memoryNullIndexer == null) { + this.memoryNullIndexer = new MemoryNullIndexer(this.reteContainer, this.tupleWidth, + this.memory.distinctValues(), this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer); + } + return this.memoryNullIndexer; + } + + @Override + public ProjectionIndexer getIdentityIndexer() { + if (this.memoryIdentityIndexer == null) { + this.memoryIdentityIndexer = new MemoryIdentityIndexer(this.reteContainer, this.tupleWidth, + this.memory.distinctValues(), this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer); + } + return this.memoryIdentityIndexer; + } + + @Override + public CommunicationGroup getCurrentGroup() { + return currentGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.currentGroup = currentGroup; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * A filter node that keeps only those tuples that contain a certain value at a certain position. + * + * @author Bergmann Gabor + * + */ +public class ValueBinderFilterNode extends FilterNode { + + int bindingIndex; + Object bindingValue; + + /** + * @param reteContainer + * @param bindingIndex + * the position in the tuple that should be bound + * @param bindingValue + * the value to which the tuple has to be bound + */ + public ValueBinderFilterNode(ReteContainer reteContainer, int bindingIndex, Object bindingValue) { + super(reteContainer); + this.bindingIndex = bindingIndex; + this.bindingValue = bindingValue; + } + + @Override + public boolean check(Tuple ps) { + return bindingValue.equals(ps.get(bindingIndex)); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +public class ActiveNodeConflictTrace extends RecipeTraceInfo { // TODO implement PatternTraceInfo + RecipeTraceInfo inactiveRecipeTrace; + public ActiveNodeConflictTrace(ReteNodeRecipe recipe, + RecipeTraceInfo parentRecipeTrace, + RecipeTraceInfo inactiveRecipeTrace) { + super(recipe, parentRecipeTrace); + this.inactiveRecipeTrace = inactiveRecipeTrace; + } + public RecipeTraceInfo getInactiveRecipeTrace() { + return inactiveRecipeTrace; + } +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * Indicates that recipe expresses the finished match set of a query. + * @author Bergmann Gabor + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class CompiledQuery extends RecipeTraceInfo implements + PatternTraceInfo { + + private PQuery query; + private final Map parentRecipeTracesPerBody; + + /** + * @since 1.6 + */ + public CompiledQuery(ReteNodeRecipe recipe, + Map parentRecipeTraces, + PQuery query) { + super(recipe, parentRecipeTraces.values()); + parentRecipeTracesPerBody = parentRecipeTraces; + this.query = query; + } + public PQuery getQuery() { + return query; + } + + @Override + public String getPatternName() { + return query.getFullyQualifiedName(); + } + + /** + * @since 1.6 + */ + public Map getParentRecipeTracesPerBody() { + return parentRecipeTracesPerBody; + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker associating a Rete recipe with a query SubPlan. + * + *

The Rete node represented by the recipe is equivalent to the SubPlan. + *

Invariant: each variable has at most one index associated with it in the tuple, i.e. no duplicates. + */ +public class CompiledSubPlan extends PlanningTrace { + + public CompiledSubPlan(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(subPlan, variablesTuple, recipe, parentRecipeTraces); + + // Make sure that each variable occurs only once + Set variablesSet = new HashSet(variablesTuple); + Preconditions.checkState(variablesSet.size() == variablesTuple.size(), + () -> String.format( + "Illegal column duplication (%s) while the query plan %s was compiled into a Rete Recipe %s", + variablesTuple.stream().map(PVariable::getName).collect(Collectors.joining(",")), + subPlan.toShortString(), recipe)); + } + + public CompiledSubPlan(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces)); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * The recipe projects the finished results of a {@link PBody} onto the list of parameters. + * @author Bergmann Gabor + * + */ +public class ParameterProjectionTrace extends RecipeTraceInfo implements PatternTraceInfo { + + public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(body, recipe, Arrays.asList(parentRecipeTraces)); + } + + public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + this.body = body; + } + + PBody body; + + @Override + public String getPatternName() { + return body.getPattern().getFullyQualifiedName(); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +/** + * One kind of trace marker that merely establishes the pattern for which the node was built. + * @author Bergmann Gabor + */ +public interface PatternTraceInfo extends TraceInfo { + String getPatternName(); +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker associating a Rete recipe with a query SubPlan. + * + *

The recipe may be an auxiliary node; + * see {@link CompiledSubPlan} if it represents the entire SubPlan instead. + */ +public class PlanningTrace extends RecipeTraceInfo implements PatternTraceInfo { + + protected SubPlan subPlan; + protected List variablesTuple; + protected Map posMapping; + + public PlanningTrace(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + this.subPlan = subPlan; + this.variablesTuple = variablesTuple; + + this.posMapping = new HashMap(); + for (int i = 0; i < variablesTuple.size(); ++i) + posMapping.put(variablesTuple.get(i), i); + } + + public PlanningTrace(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces)); + } + + public SubPlan getSubPlan() { + return subPlan; + } + + @Override + public String getPatternName() { + return subPlan.getBody().getPattern().getFullyQualifiedName(); + } + + public List getVariablesTuple() { + return variablesTuple; + } + + public Map getPosMapping() { + return posMapping; + } + + /** + * Returns a new clone that reinterprets the same compiled form + * as the compiled form of a (potentially different) subPlan. + * Useful e.g. if child plan turns out to be a no-op, or when promoting a {@link PlanningTrace} to {@link CompiledSubPlan}. + */ + public CompiledSubPlan cloneFor(SubPlan newSubPlan) { + return new CompiledSubPlan(newSubPlan, + getVariablesTuple(), + getRecipe(), + getParentRecipeTracesForCloning()); + } + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker that indicates the recipe for which the node was built. + * @author Bergmann Gabor + */ +public class RecipeTraceInfo implements TraceInfo { + public ReteNodeRecipe getRecipe() {return recipe;} + /** + * For cloning in case of recursion cut-off points, use {@link #getParentRecipeTracesForCloning()} instead. + * @return an unmodifiable view on parent traces, to be constructed before this node (or alongside, in case of recursion) + */ + public List getParentRecipeTraces() {return Collections.unmodifiableList(new ArrayList<>(parentRecipeTraces));} + /** + * Directly return the underlying collection so that changes to it will be transparent. Use only for recursion-tolerant cloning. + * @noreference This method is not intended to be referenced by clients. + */ + public Collection getParentRecipeTracesForCloning() {return parentRecipeTraces;} + @Override + public Node getNode() {return node;} + + private Node node; + ReteNodeRecipe recipe; + ReteNodeRecipe shadowedRecipe; + Collection parentRecipeTraces; + + + public RecipeTraceInfo(ReteNodeRecipe recipe, Collection parentRecipeTraces) { + super(); + this.recipe = recipe; + this.parentRecipeTraces = parentRecipeTraces; //ParentTraceList.from(parentRecipeTraces); + } + public RecipeTraceInfo(ReteNodeRecipe recipe, RecipeTraceInfo... parentRecipeTraces) { + this(recipe, Arrays.asList(parentRecipeTraces)); + } + + @Override + public boolean propagateToIndexerParent() {return false;} + @Override + public boolean propagateFromIndexerToSupplierParent() {return false;} + @Override + public boolean propagateFromStandardNodeToSupplierParent() {return false;} + @Override + public boolean propagateToProductionNodeParentAlso() {return false;} + @Override + public void assignNode(Node node) {this.node = node;} + + /** + * @param knownRecipe a known recipe that is equivalent to the current recipe + */ + public void shadowWithEquivalentRecipe(ReteNodeRecipe knownRecipe) { + this.shadowedRecipe = this.recipe; + this.recipe = knownRecipe; + } + + /** + * Get original recipe shadowed by an equivalent + */ + public ReteNodeRecipe getShadowedRecipe() { + return shadowedRecipe; + } + + +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import tools.refinery.viatra.runtime.rete.network.Node; + + +/** + * Traces the node back to a purpose for which the node was built, + * to explain why the node is there and what it means. + * @author Bergmann Gabor + */ +public interface TraceInfo { + boolean propagateToIndexerParent(); + boolean propagateFromIndexerToSupplierParent(); + boolean propagateFromStandardNodeToSupplierParent(); + boolean propagateToProductionNodeParentAlso(); + + void assignNode(Node node); + Node getNode(); +} +// /** +// * The semantics of the tuples contained in this node. +// * @return a tuple of correct size representing the semantics of each position. +// * @post not null +// */ +// 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +// private class AggregatorReferenceIndexTraceInfo extends RecipeTraceInfo { +// RecipeTraceInfo aggregatorNodeRecipeTrace; +// public AggregatorReferenceIndexTraceInfo(ProjectionIndexerRecipe recipe, +// RecipeTraceInfo parentRecipeTrace, +// RecipeTraceInfo aggregatorNodeRecipeTrace) { +// super(recipe, parentRecipeTrace); +// this.aggregatorNodeRecipeTrace = aggregatorNodeRecipeTrace; +// } +// public RecipeTraceInfo getAggregatorNodeRecipeTrace() { +// return aggregatorNodeRecipeTrace; +// } +// } +public class UserRequestTrace extends RecipeTraceInfo { + public UserRequestTrace(ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + } + public UserRequestTrace(ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + super(recipe, parentRecipeTraces); + } +} \ 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.util; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * A comparator that compares two iterables based on the lexicographic sorting induced by a comparator on elements. + * @author Bergmann Gabor + * + */ +public class LexicographicComparator implements Comparator> { + + final Comparator elementComparator; + + public LexicographicComparator(Comparator elementComparator) { + super(); + this.elementComparator = elementComparator; + } + + @Override + public int compare(Iterable o1, Iterable o2) { + Iterator it1 = o1.iterator(); + Iterator it2 = o2.iterator(); + + boolean has1, has2, bothHaveNext; + do { + has1 = it1.hasNext(); + has2 = it2.hasNext(); + bothHaveNext = has1 && has2; + if (bothHaveNext) { + T element1 = it1.next(); + T element2 = it2.next(); + int elementComparison = elementComparator.compare(element1, element2); + if (elementComparison != 0) + return elementComparison; + } + } while (bothHaveNext); + if (has1 && !has2) { + return +1; + } else if (!has1 && has2) { + return -1; + } else /*if (!has1 && !has2)*/ { + return 0; + } + } + + + + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.util; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.rete.construction.basiclinear.BasicLinearLayout; +import tools.refinery.viatra.runtime.rete.construction.quasitree.QuasiTreeLayout; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationGroup; + +/** + * Feature switches. + * @author Gabor Bergmann + * @noreference + */ +public class Options { + + public enum NodeSharingOption { + NEVER, // not recommended, patternmatcher leaks possible + INDEXER_AND_REMOTEPROXY, ALL + } + + public static final NodeSharingOption nodeSharingOption = NodeSharingOption.ALL; + public static final boolean releaseOnetimeIndexers = true; // effective only + // with + // nodesharing + // ==NEVER + + public enum InjectivityStrategy { + EAGER, LAZY + } + + public static final InjectivityStrategy injectivityStrategy = InjectivityStrategy.EAGER; + + public static final boolean enableInheritance = true; + + // public final static boolean useComplementerMask = true; + + public static final boolean employTrivialIndexers = true; + + // public final static boolean synchronous = false; + + public static final int numberOfLocalContainers = 1; + public static final int firstFreeContainer = 0; // 0 if head container is + // free to contain pattern + // bodies, 1 otherwise + + /** + * Enable for internal debugging of Rete communication scheme; + * catches cases where the topological sort is violated by a message sent "backwards" + * @since 1.6 + */ + public static final boolean MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING = false; + + /** + * Enable for internal debugging of message delivery in {@link TimelyCommunicationGroup}s; + * catches cases when there is a violation of increasing timestamps during message delivery within a group. + * @since 2.3 + */ + public static final boolean MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS = false; + + /** + * + * @author Gabor Bergmann + * @noreference + */ + public enum BuilderMethod { + LEGACY, // ONLY with GTASM + PSYSTEM_BASIC_LINEAR, PSYSTEM_QUASITREE; + /** + * @since 1.5 + */ + public IQueryPlannerStrategy layoutStrategy(IQueryBackendContext bContext, IQueryBackendHintProvider hintProvider) { + switch (this) { + case PSYSTEM_BASIC_LINEAR: + return new BasicLinearLayout(bContext); + case PSYSTEM_QUASITREE: + return new QuasiTreeLayout(bContext, hintProvider); + default: + throw new UnsupportedOperationException(); + } + } + } + + public static final BuilderMethod builderMethod = + // BuilderMethod.PSYSTEM_BASIC_LINEAR; + BuilderMethod.PSYSTEM_QUASITREE; + + public enum FunctionalDependencyOption { + OFF, + OPPORTUNISTIC + } + public static final FunctionalDependencyOption functionalDependencyOption = + FunctionalDependencyOption.OPPORTUNISTIC; + + public enum PlanTrimOption { + OFF, + OPPORTUNISTIC + } + public static final PlanTrimOption planTrimOption = + PlanTrimOption.OPPORTUNISTIC; + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.util; + +import java.util.Comparator; + +/** + * Comparing agent for an ordering. Terminology: the "preferred" item will register as LESS. + * + * @author Gabor Bergmann + * + */ +public abstract class OrderingCompareAgent { + protected T a; + protected T b; + + /** + * @param a + * @param b + */ + public OrderingCompareAgent(T a, T b) { + super(); + this.a = a; + this.b = b; + } + + int result = 0; + + protected abstract void doCompare(); + + /** + * @return the result + */ + public int compare() { + doCompare(); + return result; + } + + // COMPARISON HELPERS + protected boolean isUnknown() { + return result == 0; + } + + /** + * @pre result == 0 + */ + protected boolean consider(int partial) { + if (isUnknown()) + result = partial; + return isUnknown(); + } + + protected boolean swallowBoolean(boolean x) { + return x; + } + + // PREFERENCE FUNCTIONS + protected static int dontCare() { + return 0; + } + + protected static int preferTrue(boolean b1, boolean b2) { + return (b1 ^ b2) ? (b1 ? -1 : +1) : 0; + } + + protected static int preferFalse(boolean b1, boolean b2) { + return (b1 ^ b2) ? (b2 ? -1 : +1) : 0; + } + + protected static int preferLess(Comparable c1, U c2) { + return c1.compareTo(c2); + } + + protected static int preferLess(U c1, U c2, Comparator comp) { + return comp.compare(c1, c2); + } + + protected static int preferMore(Comparable c1, U c2) { + return -c1.compareTo(c2); + } + protected static int preferMore(U c1, U c2, Comparator comp) { + return -comp.compare(c1, c2); + } + +} 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 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.util; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; +import tools.refinery.viatra.runtime.rete.matcher.DRedReteBackendFactory; + +/** + * Provides key objects (of type {@link QueryHintOption}) for {@link QueryEvaluationHint}s. + * @author Gabor Bergmann + * @since 1.5 + */ +public final class ReteHintOptions { + + private ReteHintOptions() {/*Utility class constructor*/} + + public static final QueryHintOption useDiscriminatorDispatchersForConstantFiltering = + hintOption("useDiscriminatorDispatchersForConstantFiltering", true); + + public static final QueryHintOption prioritizeConstantFiltering = + hintOption("prioritizeConstantFiltering", true); + + public static final QueryHintOption cacheOutputOfEvaluatorsByDefault = + hintOption("cacheOutputOfEvaluatorsByDefault", true); + + /** + * The incremental query evaluator backend can evaluate recursive patterns. + * However, by default, instance models that contain cycles are not supported with recursive queries + * and can lead to incorrect query results. + * Enabling Delete And Rederive (DRED) mode guarantees that recursive query evaluation leads to correct results in these cases as well. + * + *

As DRED may diminish the performance of incremental maintenance, it is not enabled by default. + * @since 1.6 + * @deprecated Use {@link DRedReteBackendFactory} instead of setting this option to true. + */ + @Deprecated + public static final QueryHintOption deleteRederiveEvaluation = + hintOption("deleteRederiveEvaluation", false); + + /** + * This hint allows the query planner to take advantage of "weakened alternative" suggestions of the meta context. + * For instance, enumerable unary type constraints may be substituted with a simple type filtering where sufficient. + * + * @since 1.6 + */ + public static final QueryHintOption expandWeakenedAlternativeConstraints = + hintOption("expandWeakenedAlternativeConstraints", true); + + // internal helper for conciseness + private static QueryHintOption hintOption(String hintKeyLocalName, T defaultValue) { + return new QueryHintOption<>(ReteHintOptions.class, hintKeyLocalName, defaultValue); + } +} -- cgit v1.2.3-54-g00ecf