From 97b0c4c1192fe5580a7957c844acc8092b56c604 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 16 Sep 2023 13:19:31 +0200 Subject: chore: remove VIATRA branding Rename VIATRA subprojects to Refinery Interpreter to avoid interfering with Eclipse Foundation trademarks. Uses refering to a specific (historical) version of VIATRA were kept to avoid ambiguity. --- .../aggregation/AbstractColumnAggregatorNode.java | 474 +++++++++++ .../rete/aggregation/ColumnAggregatorNode.java | 369 ++++++++ .../interpreter/rete/aggregation/CountNode.java | 38 + .../interpreter/rete/aggregation/GroupedMap.java | 120 +++ .../interpreter/rete/aggregation/GroupedSet.java | 114 +++ .../rete/aggregation/IAggregatorNode.java | 26 + .../aggregation/IndexerBasedAggregatorNode.java | 278 ++++++ ...FaithfulParallelTimelyColumnAggregatorNode.java | 212 +++++ ...ithfulSequentialTimelyColumnAggregatorNode.java | 279 ++++++ .../timely/FaithfulTimelyColumnAggregatorNode.java | 247 ++++++ ...irstOnlyParallelTimelyColumnAggregatorNode.java | 106 +++ ...stOnlySequentialTimelyColumnAggregatorNode.java | 117 +++ .../FirstOnlyTimelyColumnAggregatorNode.java | 212 +++++ .../interpreter/rete/boundary/Disconnectable.java | 26 + .../rete/boundary/ExternalInputEnumeratorNode.java | 209 +++++ .../boundary/ExternalInputStatelessFilterNode.java | 68 ++ .../interpreter/rete/boundary/InputConnector.java | 208 +++++ .../interpreter/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 | 947 +++++++++++++++++++++ .../rete/construction/quasitree/JoinCandidate.java | 162 ++++ .../quasitree/JoinOrderingHeuristics.java | 49 ++ .../construction/quasitree/QuasiTreeLayout.java | 205 +++++ .../rete/construction/quasitree/TieBreaker.java | 30 + .../rete/eval/AbstractEvaluatorNode.java | 65 ++ .../interpreter/rete/eval/EvaluatorCore.java | 180 ++++ .../interpreter/rete/eval/IEvaluatorNode.java | 25 + .../rete/eval/MemorylessEvaluatorNode.java | 75 ++ .../rete/eval/OutputCachingEvaluatorNode.java | 311 +++++++ .../rete/eval/RelationEvaluatorNode.java | 183 ++++ .../rete/index/DefaultIndexerListener.java | 28 + .../interpreter/rete/index/DualInputNode.java | 347 ++++++++ .../interpreter/rete/index/ExistenceNode.java | 199 +++++ .../rete/index/GenericProjectionIndexer.java | 76 ++ .../interpreter/rete/index/IdentityIndexer.java | 76 ++ .../refinery/interpreter/rete/index/Indexer.java | 71 ++ .../interpreter/rete/index/IndexerListener.java | 41 + .../interpreter/rete/index/IndexerWithMemory.java | 284 ++++++ .../interpreter/rete/index/IterableIndexer.java | 34 + .../refinery/interpreter/rete/index/JoinNode.java | 193 +++++ .../rete/index/MemoryIdentityIndexer.java | 55 ++ .../interpreter/rete/index/MemoryNullIndexer.java | 54 ++ .../interpreter/rete/index/NullIndexer.java | 88 ++ .../interpreter/rete/index/OnetimeIndexer.java | 48 ++ .../interpreter/rete/index/ProjectionIndexer.java | 21 + .../rete/index/SpecializedProjectionIndexer.java | 176 ++++ .../interpreter/rete/index/StandardIndexer.java | 127 +++ .../rete/index/TransitiveClosureNodeIndexer.java | 121 +++ .../index/timely/TimelyMemoryIdentityIndexer.java | 51 ++ .../rete/index/timely/TimelyMemoryNullIndexer.java | 49 ++ .../rete/itc/alg/counting/CountingAlg.java | 226 +++++ .../rete/itc/alg/counting/CountingTcRelation.java | 259 ++++++ .../rete/itc/alg/incscc/CountingListener.java | 36 + .../interpreter/rete/itc/alg/incscc/IncSCCAlg.java | 609 +++++++++++++ .../rete/itc/alg/misc/DFSPathFinder.java | 146 ++++ .../interpreter/rete/itc/alg/misc/Edge.java | 38 + .../interpreter/rete/itc/alg/misc/GraphHelper.java | 169 ++++ .../rete/itc/alg/misc/IGraphPathFinder.java | 67 ++ .../interpreter/rete/itc/alg/misc/ITcRelation.java | 31 + .../interpreter/rete/itc/alg/misc/Tuple.java | 60 ++ .../interpreter/rete/itc/alg/misc/bfs/BFS.java | 148 ++++ .../interpreter/rete/itc/alg/misc/scc/PKAlg.java | 179 ++++ .../interpreter/rete/itc/alg/misc/scc/SCC.java | 143 ++++ .../rete/itc/alg/misc/scc/SCCProperty.java | 37 + .../rete/itc/alg/misc/scc/SCCResult.java | 81 ++ .../itc/alg/misc/topsort/TopologicalSorting.java | 73 ++ .../RepresentativeElectionAlgorithm.java | 174 ++++ .../alg/representative/RepresentativeObserver.java | 12 + .../StronglyConnectedComponentAlgorithm.java | 69 ++ .../WeaklyConnectedComponentAlgorithm.java | 85 ++ .../rete/itc/alg/util/CollectionHelper.java | 64 ++ .../rete/itc/graphimpl/DotGenerator.java | 160 ++++ .../interpreter/rete/itc/graphimpl/Graph.java | 185 ++++ .../itc/igraph/IBiDirectionalGraphDataSource.java | 37 + .../rete/itc/igraph/IBiDirectionalWrapper.java | 110 +++ .../rete/itc/igraph/IGraphDataSource.java | 70 ++ .../rete/itc/igraph/IGraphObserver.java | 55 ++ .../interpreter/rete/itc/igraph/ITcDataSource.java | 82 ++ .../interpreter/rete/itc/igraph/ITcObserver.java | 39 + .../rete/matcher/DRedReteBackendFactory.java | 49 ++ .../interpreter/rete/matcher/HintConfigurator.java | 46 + .../rete/matcher/IncrementalMatcherCapability.java | 30 + .../rete/matcher/ReteBackendFactory.java | 100 +++ .../rete/matcher/ReteBackendFactoryProvider.java | 35 + .../interpreter/rete/matcher/ReteEngine.java | 579 +++++++++++++ .../rete/matcher/RetePatternMatcher.java | 453 ++++++++++ .../rete/matcher/TimelyConfiguration.java | 61 ++ .../rete/matcher/TimelyReteBackendFactory.java | 62 ++ .../tools/refinery/interpreter/rete/misc/Bag.java | 43 + .../interpreter/rete/misc/ConstantNode.java | 50 ++ .../interpreter/rete/misc/DefaultDeltaMonitor.java | 43 + .../interpreter/rete/misc/DeltaMonitor.java | 111 +++ .../interpreter/rete/misc/SimpleReceiver.java | 109 +++ .../interpreter/rete/network/BaseNode.java | 108 +++ .../rete/network/ConnectionFactory.java | 170 ++++ .../interpreter/rete/network/IGroupable.java | 31 + .../refinery/interpreter/rete/network/Network.java | 404 +++++++++ .../NetworkStructureChangeSensitiveNode.java | 30 + .../refinery/interpreter/rete/network/Node.java | 62 ++ .../interpreter/rete/network/NodeFactory.java | 376 ++++++++ .../interpreter/rete/network/NodeProvisioner.java | 346 ++++++++ .../rete/network/PosetAwareReceiver.java | 39 + .../interpreter/rete/network/ProductionNode.java | 28 + .../interpreter/rete/network/Receiver.java | 85 ++ .../interpreter/rete/network/RederivableNode.java | 34 + .../rete/network/ReinitializedNode.java | 14 + .../interpreter/rete/network/ReteContainer.java | 732 ++++++++++++++++ .../interpreter/rete/network/StandardNode.java | 123 +++ .../interpreter/rete/network/Supplier.java | 82 ++ .../refinery/interpreter/rete/network/Tunnel.java | 19 + .../interpreter/rete/network/UpdateMessage.java | 31 + .../network/communication/CommunicationGroup.java | 103 +++ .../communication/CommunicationTracker.java | 454 ++++++++++ .../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 + .../interpreter/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 | 163 ++++ .../mailbox/timeless/PosetAwareMailbox.java | 218 +++++ .../mailbox/timeless/UpdateSplittingMailbox.java | 135 +++ .../rete/network/mailbox/timely/TimelyMailbox.java | 150 ++++ .../refinery/interpreter/rete/remote/Address.java | 125 +++ .../interpreter/rete/remote/RemoteReceiver.java | 63 ++ .../interpreter/rete/remote/RemoteSupplier.java | 54 ++ .../single/AbstractUniquenessEnforcerNode.java | 138 +++ .../interpreter/rete/single/CallbackNode.java | 37 + .../rete/single/DefaultProductionNode.java | 79 ++ .../rete/single/DiscriminatorBucketNode.java | 85 ++ .../rete/single/DiscriminatorDispatcherNode.java | 154 ++++ .../rete/single/EqualityFilterNode.java | 41 + .../interpreter/rete/single/FilterNode.java | 69 ++ .../rete/single/InequalityFilterNode.java | 52 ++ .../rete/single/RepresentativeElectionNode.java | 125 +++ .../interpreter/rete/single/SingleInputNode.java | 126 +++ .../rete/single/TimelyProductionNode.java | 64 ++ .../rete/single/TimelyUniquenessEnforcerNode.java | 161 ++++ .../interpreter/rete/single/TransformerNode.java | 49 ++ .../rete/single/TransitiveClosureNode.java | 147 ++++ .../interpreter/rete/single/TransparentNode.java | 48 ++ .../interpreter/rete/single/TrimmerNode.java | 61 ++ .../rete/single/UniquenessEnforcerNode.java | 321 +++++++ .../rete/single/ValueBinderFilterNode.java | 44 + .../rete/traceability/ActiveNodeConflictTrace.java | 24 + .../rete/traceability/CompiledQuery.java | 54 ++ .../rete/traceability/CompiledSubPlan.java | 51 ++ .../traceability/ParameterProjectionTrace.java | 42 + .../rete/traceability/PatternTraceInfo.java | 17 + .../rete/traceability/PlanningTrace.java | 80 ++ .../rete/traceability/RecipeTraceInfo.java | 81 ++ .../interpreter/rete/traceability/TraceInfo.java | 33 + .../rete/traceability/UserRequestTrace.java | 36 + .../rete/util/LexicographicComparator.java | 58 ++ .../refinery/interpreter/rete/util/Options.java | 111 +++ .../rete/util/OrderingCompareAgent.java | 97 +++ .../interpreter/rete/util/ReteHintOptions.java | 60 ++ 180 files changed, 22742 insertions(+) create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/AbstractColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/ColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/CountNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedMap.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedSet.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IndexerBasedAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/Disconnectable.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputEnumeratorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputStatelessFilterNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/InputConnector.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ReteBoundary.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/RetePatternBuildException.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/BasicLinearLayout.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/OrderingHeuristics.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/CompilerHelper.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/RecursionCutoffPoint.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinCandidate.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinOrderingHeuristics.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/QuasiTreeLayout.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/TieBreaker.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/AbstractEvaluatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/EvaluatorCore.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/IEvaluatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/MemorylessEvaluatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/OutputCachingEvaluatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/RelationEvaluatorNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DefaultIndexerListener.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DualInputNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ExistenceNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/GenericProjectionIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IdentityIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/Indexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerListener.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerWithMemory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IterableIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/JoinNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryIdentityIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryNullIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/NullIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/OnetimeIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ProjectionIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/SpecializedProjectionIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/StandardIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/TransitiveClosureNodeIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryIdentityIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryNullIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingAlg.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingTcRelation.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/CountingListener.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/IncSCCAlg.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/DFSPathFinder.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Edge.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/GraphHelper.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/IGraphPathFinder.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/ITcRelation.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Tuple.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/bfs/BFS.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/PKAlg.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCC.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCProperty.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCResult.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/topsort/TopologicalSorting.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeObserver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/util/CollectionHelper.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/DotGenerator.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/Graph.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalGraphDataSource.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalWrapper.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphDataSource.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphObserver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcDataSource.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcObserver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/DRedReteBackendFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/HintConfigurator.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/IncrementalMatcherCapability.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactoryProvider.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteEngine.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/RetePatternMatcher.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyConfiguration.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyReteBackendFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/Bag.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/ConstantNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DefaultDeltaMonitor.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DeltaMonitor.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/SimpleReceiver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/BaseNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/IGroupable.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Network.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NetworkStructureChangeSensitiveNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Node.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/PosetAwareReceiver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ProductionNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Receiver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/RederivableNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReinitializedNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReteContainer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/StandardNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Supplier.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Tunnel.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/UpdateMessage.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationGroup.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationTracker.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/MessageSelector.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/NodeComparator.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/PhasedSelector.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/Timestamp.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/RecursiveCommunicationGroup.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/SingletonCommunicationGroup.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/TimelessCommunicationTracker.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/ResumableNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationGroup.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationTracker.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyIndexerListenerProxy.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyMailboxProxy.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimestampTransformation.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedCommand.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedConnectCommand.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedDisconnectCommand.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/DefaultMessageIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/GroupBasedMessageIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/MessageIndexer.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/AdaptableMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/FallThroughCapableMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/Mailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/MessageIndexerFactory.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/BehaviorChangingMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/DefaultMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/PosetAwareMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/UpdateSplittingMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timely/TimelyMailbox.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/Address.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteReceiver.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteSupplier.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/AbstractUniquenessEnforcerNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/CallbackNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DefaultProductionNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorBucketNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorDispatcherNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/EqualityFilterNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/FilterNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/InequalityFilterNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/RepresentativeElectionNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/SingleInputNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyProductionNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyUniquenessEnforcerNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransformerNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransitiveClosureNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransparentNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TrimmerNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/UniquenessEnforcerNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/ValueBinderFilterNode.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ActiveNodeConflictTrace.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledQuery.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledSubPlan.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ParameterProjectionTrace.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PatternTraceInfo.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PlanningTrace.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/RecipeTraceInfo.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/TraceInfo.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/UserRequestTrace.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/LexicographicComparator.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/Options.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/OrderingCompareAgent.java create mode 100644 subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/ReteHintOptions.java (limited to 'subprojects/interpreter-rete/src/main/java') diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/AbstractColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/AbstractColumnAggregatorNode.java new file mode 100644 index 00000000..3cf3f642 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.StandardIndexer; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/ColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/ColumnAggregatorNode.java new file mode 100644 index 00000000..b1a25807 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation; + +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.network.PosetAwareReceiver; +import tools.refinery.interpreter.rete.network.RederivableNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/CountNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/CountNode.java new file mode 100644 index 00000000..644bef4a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation; + +import java.util.Collection; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedMap.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedMap.java new file mode 100644 index 00000000..4e4ac62d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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); + }); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedSet.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/GroupedSet.java new file mode 100644 index 00000000..b2700084 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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(); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IAggregatorNode.java new file mode 100644 index 00000000..1d30cfce --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation; + +import tools.refinery.interpreter.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); + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IndexerBasedAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/IndexerBasedAggregatorNode.java new file mode 100644 index 00000000..e689c0eb --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.DefaultIndexerListener; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.index.StandardIndexer; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.communication.Timestamp; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..13cd9662 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.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.interpreter.rete.aggregation.timely; + +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.*; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +/** + * 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; + } + + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..72e52f7e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java @@ -0,0 +1,279 @@ +/******************************************************************************* + * 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.interpreter.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.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.IDeltaBag; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.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; + } + + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..89486f7d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.matchers.util.timeline.Timelines; +import tools.refinery.interpreter.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.GroupedMap; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.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); + + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..9f5c55eb --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation.timely; + +import java.util.Map.Entry; +import java.util.TreeMap; + +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..c90115c9 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.aggregation.timely; + +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..da076681 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.matchers.util.timeline.Timelines; +import tools.refinery.interpreter.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.GroupedMap; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/Disconnectable.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/Disconnectable.java new file mode 100644 index 00000000..9e9992f8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputEnumeratorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputEnumeratorNode.java new file mode 100644 index 00000000..6ea60ac3 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.matcher.ReteEngine; +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputStatelessFilterNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ExternalInputStatelessFilterNode.java new file mode 100644 index 00000000..32054481 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.boundary; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.rete.matcher.ReteEngine; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/InputConnector.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/InputConnector.java new file mode 100644 index 00000000..3aeaf9ec --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.recipes.InputFilterRecipe; +import tools.refinery.interpreter.rete.recipes.InputRecipe; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ReteBoundary.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/boundary/ReteBoundary.java new file mode 100644 index 00000000..6be2aaea --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.boundary; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.matcher.ReteEngine; +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.rete.traceability.CompiledQuery; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/RetePatternBuildException.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/RetePatternBuildException.java new file mode 100644 index 00000000..592a343f --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.construction; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/BasicLinearLayout.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/BasicLinearLayout.java new file mode 100644 index 00000000..cfcc4ee0 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.construction.basiclinear; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.SubPlanFactory; +import tools.refinery.interpreter.matchers.planning.helpers.BuildHelper; +import tools.refinery.interpreter.matchers.planning.operations.PApply; +import tools.refinery.interpreter.matchers.planning.operations.PProject; +import tools.refinery.interpreter.matchers.planning.operations.PStart; +import tools.refinery.interpreter.matchers.psystem.DeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.Equality; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/OrderingHeuristics.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/basiclinear/OrderingHeuristics.java new file mode 100644 index 00000000..9dfb70f8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.construction.basiclinear; + +import java.util.Comparator; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.DeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/CompilerHelper.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/CompilerHelper.java new file mode 100644 index 00000000..9df1f545 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.recipes.IndexerBasedAggregatorRecipe; +import tools.refinery.interpreter.rete.recipes.MonotonicityInfo; +import tools.refinery.interpreter.rete.recipes.ProjectionIndexerRecipe; +import tools.refinery.interpreter.rete.recipes.ReteNodeRecipe; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.helpers.TypeHelper; +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.recipes.EqualityFilterRecipe; +import tools.refinery.interpreter.rete.recipes.IndexerRecipe; +import tools.refinery.interpreter.rete.recipes.JoinRecipe; +import tools.refinery.interpreter.rete.recipes.Mask; +import tools.refinery.interpreter.rete.recipes.ProductionRecipe; +import tools.refinery.interpreter.rete.recipes.RecipesFactory; +import tools.refinery.interpreter.rete.recipes.SingleColumnAggregatorRecipe; +import tools.refinery.interpreter.rete.recipes.TrimmerRecipe; +import tools.refinery.interpreter.rete.recipes.helper.RecipesHelper; +import tools.refinery.interpreter.rete.traceability.CompiledQuery; +import tools.refinery.interpreter.rete.traceability.CompiledSubPlan; +import tools.refinery.interpreter.rete.traceability.PlanningTrace; +import tools.refinery.interpreter.rete.traceability.RecipeTraceInfo; +import tools.refinery.interpreter.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(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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/RecursionCutoffPoint.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/RecursionCutoffPoint.java new file mode 100644 index 00000000..24d001e8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.recipes.ReteNodeRecipe; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.recipes.ProductionRecipe; +import tools.refinery.interpreter.rete.traceability.CompiledQuery; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java new file mode 100644 index 00000000..f84eae0a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/plancompiler/ReteRecipeCompiler.java @@ -0,0 +1,947 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, 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.interpreter.rete.construction.plancompiler; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.CommonQueryHintOptions; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.context.IQueryCacheContext; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.helpers.BuildHelper; +import tools.refinery.interpreter.matchers.planning.operations.*; +import tools.refinery.interpreter.matchers.psystem.*; +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.*; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PVisibility; +import tools.refinery.interpreter.matchers.psystem.rewriters.*; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.recipes.*; +import tools.refinery.interpreter.rete.recipes.helper.RecipesHelper; +import tools.refinery.interpreter.rete.traceability.*; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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()); + + CompilerHelper.JoinHelper joinHelper = new CompilerHelper.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 + CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.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 + CompilerHelper.JoinHelper fakeJoinHelper = new CompilerHelper.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 + CompilerHelper.JoinHelper joinHelper = new CompilerHelper.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)) { + CompilerHelper.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinCandidate.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinCandidate.java new file mode 100644 index 00000000..ae79b21a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.SubPlanFactory; +import tools.refinery.interpreter.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.interpreter.matchers.planning.operations.PJoin; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinOrderingHeuristics.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/JoinOrderingHeuristics.java new file mode 100644 index 00000000..3278d72c --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.interpreter.rete.util.Options; +import tools.refinery.interpreter.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(); + + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/QuasiTreeLayout.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/QuasiTreeLayout.java new file mode 100644 index 00000000..85d1bdd8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.SubPlanFactory; +import tools.refinery.interpreter.matchers.planning.helpers.BuildHelper; +import tools.refinery.interpreter.matchers.planning.operations.PApply; +import tools.refinery.interpreter.matchers.planning.operations.PEnumerate; +import tools.refinery.interpreter.matchers.planning.operations.PProject; +import tools.refinery.interpreter.matchers.planning.operations.PStart; +import tools.refinery.interpreter.matchers.psystem.DeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.rete.construction.RetePatternBuildException; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/TieBreaker.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/construction/quasitree/TieBreaker.java new file mode 100644 index 00000000..7d015cd4 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/AbstractEvaluatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/AbstractEvaluatorNode.java new file mode 100644 index 00000000..ba282fca --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.eval; + +import java.util.Iterator; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.single.SingleInputNode; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/EvaluatorCore.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/EvaluatorCore.java new file mode 100644 index 00000000..b695645e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleValueProvider; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/IEvaluatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/IEvaluatorNode.java new file mode 100644 index 00000000..129f5586 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.eval; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/MemorylessEvaluatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/MemorylessEvaluatorNode.java new file mode 100644 index 00000000..805272ed --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/OutputCachingEvaluatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/OutputCachingEvaluatorNode.java new file mode 100644 index 00000000..653975ed --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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() == TimelyConfiguration.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/RelationEvaluatorNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/eval/RelationEvaluatorNode.java new file mode 100644 index 00000000..640ac43b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.misc.SimpleReceiver; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.single.AbstractUniquenessEnforcerNode; +import tools.refinery.interpreter.matchers.psystem.IRelationEvaluator; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.RelationEvaluation; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.matchers.util.timeline.Timelines; +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.Supplier; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DefaultIndexerListener.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DefaultIndexerListener.java new file mode 100644 index 00000000..6ab37044 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.lang.ref.WeakReference; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DualInputNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DualInputNode.java new file mode 100644 index 00000000..c0529d8b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/DualInputNode.java @@ -0,0 +1,347 @@ +/******************************************************************************* + * 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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.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 Timestamp.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ExistenceNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ExistenceNode.java new file mode 100644 index 00000000..7400053d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/GenericProjectionIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/GenericProjectionIndexer.java new file mode 100644 index 00000000..db5a4f0d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IdentityIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IdentityIndexer.java new file mode 100644 index 00000000..ec98e70a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/Indexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/Indexer.java new file mode 100644 index 00000000..200bdbc9 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerListener.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerListener.java new file mode 100644 index 00000000..b7a724ef --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerWithMemory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IndexerWithMemory.java new file mode 100644 index 00000000..75a72daf --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.matchers.memories.MaskedTupleMemory; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Diff; + +/** + * @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() == TimelyConfiguration.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() == TimelyConfiguration.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 + } + + }; + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IterableIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/IterableIndexer.java new file mode 100644 index 00000000..553708a1 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/JoinNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/JoinNode.java new file mode 100644 index 00000000..ab97b490 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryIdentityIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryIdentityIndexer.java new file mode 100644 index 00000000..27bd68fe --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryNullIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/MemoryNullIndexer.java new file mode 100644 index 00000000..b6e55cca --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/NullIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/NullIndexer.java new file mode 100644 index 00000000..68bb8d2d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/OnetimeIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/OnetimeIndexer.java new file mode 100644 index 00000000..52e79130 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/OnetimeIndexer.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.interpreter.rete.index; + +import java.util.Collection; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.rete.util.Options; + +/** + * @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 (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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ProjectionIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/ProjectionIndexer.java new file mode 100644 index 00000000..587ad3eb --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/SpecializedProjectionIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/SpecializedProjectionIndexer.java new file mode 100644 index 00000000..27ba3976 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/StandardIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/StandardIndexer.java new file mode 100644 index 00000000..36b58ac7 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.interpreter.rete.network.BaseNode; +import tools.refinery.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/TransitiveClosureNodeIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/TransitiveClosureNodeIndexer.java new file mode 100644 index 00000000..8b2a5270 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.alg.misc.Tuple; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.single.TransitiveClosureNode; +import tools.refinery.interpreter.rete.itc.alg.incscc.IncSCCAlg; +import tools.refinery.interpreter.matchers.tuple.MaskedTuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; + +// 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(tools.refinery.interpreter.matchers.tuple.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); + tools.refinery.interpreter.matchers.tuple.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); + tools.refinery.interpreter.matchers.tuple.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 (Tuple tuple : tuples) { + retSet.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget())); + } + return retSet; + } + + /** + * @since 2.4 + */ + public void propagate(Direction direction, tools.refinery.interpreter.matchers.tuple.Tuple updateElement, boolean change) { + propagate(direction, updateElement, new MaskedTuple(updateElement, mask), change, null); + } + + @Override + public Receiver getActiveNode() { + return tcNode; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryIdentityIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryIdentityIndexer.java new file mode 100644 index 00000000..962b3137 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.IdentityIndexer; + +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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryNullIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/index/timely/TimelyMemoryNullIndexer.java new file mode 100644 index 00000000..3ec8529b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.index.timely; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.NullIndexer; + +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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingAlg.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingAlg.java new file mode 100644 index 00000000..7382498b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingAlg.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.counting; + +import java.util.List; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.alg.misc.DFSPathFinder; +import tools.refinery.interpreter.rete.itc.alg.misc.IGraphPathFinder; +import tools.refinery.interpreter.rete.itc.alg.misc.ITcRelation; +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IGraphObserver; +import tools.refinery.interpreter.rete.itc.igraph.ITcDataSource; +import tools.refinery.interpreter.rete.itc.igraph.ITcObserver; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.IMemoryView; + +/** + * This class is the optimized implementation of the Counting algorithm. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class CountingAlg implements IGraphObserver, ITcDataSource { + + private CountingTcRelation tc = null; + private IBiDirectionalGraphDataSource gds = null; + private List> observers; + + /** + * Constructs a new Counting algorithm and initializes the transitive closure relation with the given graph data + * source. Attach itself on the graph data source as an observer. + * + * @param gds + * the graph data source instance + */ + public CountingAlg(IGraphDataSource gds) { + + if (gds instanceof IBiDirectionalGraphDataSource) { + this.gds = (IBiDirectionalGraphDataSource) gds; + } else { + this.gds = new IBiDirectionalWrapper(gds); + } + + observers = CollectionsFactory.>createObserverList(); + tc = new CountingTcRelation(true); + + initTc(); + gds.attachObserver(this); + } + + /** + * Initializes the transitive closure relation. + */ + private void initTc() { + this.setTcRelation(CountingTcRelation.createFrom(gds)); + } + + @Override + public void edgeInserted(V source, V target) { + if (!source.equals(target)) { + deriveTc(source, target, true); + } + } + + @Override + public void edgeDeleted(V source, V target) { + if (!source.equals(target)) { + deriveTc(source, target, false); + } + } + + @Override + public void nodeInserted(V n) { + + } + + @Override + public void nodeDeleted(V n) { + this.tc.deleteTupleEnd(n); + } + + /** + * Derives the transitive closure relation when an edge is inserted or deleted. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + * @param dCount + * the value is -1 if an edge was deleted and +1 if an edge was inserted + */ + private void deriveTc(V source, V target, boolean isInsertion) { + + // if (dCount == 1 && isReachable(target, source)) { + // System.out.println("The graph contains cycle with (" + source + ","+ target + ") edge!"); + // } + + CountingTcRelation dtc = new CountingTcRelation(false); + Set tupEnds = null; + + // 1. d(tc(x,y)) :- d(l(x,y)) + if (tc.updateTuple(source, target, isInsertion)) { + dtc.updateTuple(source, target, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(source, target, isInsertion); + } + + // 2. d(tc(x,y)) :- d(l(x,z)) & tc(z,y) + tupEnds = tc.getTupleEnds(target); + if (tupEnds != null) { + for (V tupEnd : tupEnds) { + if (!tupEnd.equals(source)) { + if (tc.updateTuple(source, tupEnd, isInsertion)) { + dtc.updateTuple(source, tupEnd, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(source, tupEnd, isInsertion); + } + } + } + } + + // 3. d(tc(x,y)) :- lv(x,z) & d(tc(z,y)) + CountingTcRelation newTuples = dtc; + CountingTcRelation tmp = null; + dtc = new CountingTcRelation(false); + + IMemoryView nodes = null; + + while (!newTuples.isEmpty()) { + + tmp = dtc; + dtc = newTuples; + newTuples = tmp; + newTuples.clear(); + + for (V tS : dtc.getTupleStarts()) { + nodes = gds.getSourceNodes(tS); + for (V nS : nodes.distinctValues()) { + int count = nodes.getCount(nS); + for (int i = 0; i < count; i++) { + tupEnds = dtc.getTupleEnds(tS); + if (tupEnds != null) { + for (V tT : tupEnds) { + if (!nS.equals(tT)) { + if (tc.updateTuple(nS, tT, isInsertion)) { + newTuples.updateTuple(nS, tT, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(nS, tT, isInsertion); + } + } + } + } + } + } + } + } + + // System.out.println(tc); + } + + public ITcRelation getTcRelation() { + return this.tc; + } + + public void setTcRelation(CountingTcRelation tc) { + this.tc = tc; + } + + @Override + public boolean isReachable(V source, V target) { + return tc.containsTuple(source, target); + } + + @Override + public void attachObserver(ITcObserver to) { + this.observers.add(to); + + } + + @Override + public void detachObserver(ITcObserver to) { + this.observers.remove(to); + } + + @Override + public Set getAllReachableTargets(V source) { + return tc.getTupleEnds(source); + } + + @Override + public Set getAllReachableSources(V target) { + return tc.getTupleStarts(target); + } + + private void notifyTcObservers(V source, V target, boolean isInsertion) { + if (isInsertion) { + for (ITcObserver o : observers) { + o.tupleInserted(source, target); + } + } else { + for (ITcObserver o : observers) { + o.tupleDeleted(source, target); + } + } + } + + @Override + public void dispose() { + tc.clear(); + this.gds.detachObserver(this); + } + + @Override + public IGraphPathFinder getPathFinder() { + return new DFSPathFinder(gds, this); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingTcRelation.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingTcRelation.java new file mode 100644 index 00000000..790b37ef --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/counting/CountingTcRelation.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.counting; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.rete.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.interpreter.rete.itc.alg.misc.ITcRelation; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.matchers.util.IMultiLookup.ChangeGranularity; + +/** + * Transitive closure relation implementation for the Counting algorithm. + * + * @author Tamas Szabo + * + * @param + */ +public class CountingTcRelation implements ITcRelation { + + private IMultiLookup tuplesForward = null; + private IMultiLookup tuplesBackward = null; + + protected CountingTcRelation(boolean backwardIndexing) { + tuplesForward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + if (backwardIndexing) + tuplesBackward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + } + + protected boolean isEmpty() { + return 0 == this.tuplesForward.countKeys(); + } + + protected void clear() { + this.tuplesForward.clear(); + + if (tuplesBackward != null) { + this.tuplesBackward.clear(); + } + } + + protected void union(CountingTcRelation rA) { + IMultiLookup rForward = rA.tuplesForward; + for (V source : rForward.distinctKeys()) { + IMemoryView targetBag = rForward.lookup(source); + for (V target : targetBag.distinctValues()) { + this.addTuple(source, target, targetBag.getCount(target)); + } + } + } + + public int getCount(V source, V target) { + IMemoryView bucket = tuplesForward.lookup(source); + return bucket == null ? 0 : bucket.getCount(target); + } + + /** + * Returns true if the tc relation did not contain previously such a tuple that is defined by (source,target), false + * otherwise (in this case count is incremented with the given count parameter). + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + * @param count + * the count of the tuple, must be positive + * @return true if the relation did not contain previously the tuple + */ + public boolean addTuple(V source, V target, int count) { + if (tuplesBackward != null) { + tuplesBackward.addPairPositiveMultiplicity(target, source, count); + } + + ChangeGranularity change = + tuplesForward.addPairPositiveMultiplicity(source, target, count); + + return change != ChangeGranularity.DUPLICATE; + } + + /** + * Derivation count of the tuple (source,target) is incremented or decremented. + * Returns true iff updated to / from zero derivation count. + * @since 1.7 + */ + public boolean updateTuple(V source, V target, boolean isInsertion) { + if (isInsertion) { + if (tuplesBackward != null) { + tuplesBackward.addPair(target, source); + } + ChangeGranularity change = + tuplesForward.addPair(source, target); + return change != ChangeGranularity.DUPLICATE; + } else { + if (tuplesBackward != null) { + tuplesBackward.removePair(target, source); + } + ChangeGranularity change = + tuplesForward.removePair(source, target); + return change != ChangeGranularity.DUPLICATE; + } + } + + public void deleteTupleEnd(V deleted) { + Set sourcesToDelete = CollectionsFactory.createSet(); + Set targetsToDelete = CollectionsFactory.createSet(); + + for (V target : tuplesForward.lookupOrEmpty(deleted).distinctValues()) { + targetsToDelete.add(target); + } + if (tuplesBackward != null) { + for (V source : tuplesBackward.lookupOrEmpty(deleted).distinctValues()) { + sourcesToDelete.add(source); + } + } else { + for (V sourceCandidate : tuplesForward.distinctKeys()) { + if (tuplesForward.lookupOrEmpty(sourceCandidate).containsNonZero(deleted)) + sourcesToDelete.add(sourceCandidate); + } + } + + for (V source : sourcesToDelete) { + int count = tuplesForward.lookupOrEmpty(source).getCount(deleted); + for (int i=0; i< count; ++i) tuplesForward.removePair(source, deleted); + } + for (V target : targetsToDelete) { + int count = tuplesForward.lookupOrEmpty(deleted).getCount(target); + for (int i=0; i< count; ++i) tuplesForward.removePair(deleted, target); + } + + if (tuplesBackward != null) { + for (V source : sourcesToDelete) { + int count = tuplesBackward.lookupOrEmpty(deleted).getCount(source); + for (int i=0; i< count; ++i) tuplesBackward.removePair(deleted, source); + } + for (V target : targetsToDelete) { + int count = tuplesBackward.lookupOrEmpty(target).getCount(deleted); + for (int i=0; i< count; ++i) tuplesBackward.removePair(target, deleted); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("TcRelation = "); + + for (V source : tuplesForward.distinctKeys()) { + IMemoryView targets = tuplesForward.lookup(source); + for (V target : targets.distinctValues()) { + sb.append("{(" + source + "," + target + ")," + targets.getCount(target) + "} "); + } + } + + return sb.toString(); + } + + @Override + public Set getTupleEnds(V source) { + IMemoryView tupEnds = tuplesForward.lookup(source); + if (tupEnds == null) + return null; + return tupEnds.distinctValues(); + } + + /** + * Returns the set of nodes from which the target node is reachable, if already computed. + * + * @param target + * the target node + * @return the set of source nodes + * @throws UnsupportedOperationException if backwards index not computed + */ + public Set getTupleStarts(V target) { + if (tuplesBackward != null) { + IMemoryView tupStarts = tuplesBackward.lookup(target); + if (tupStarts == null) + return null; + return tupStarts.distinctValues(); + } else { + throw new UnsupportedOperationException("built without backward indexing"); + } + } + + @Override + public Set getTupleStarts() { + Set nodes = CollectionsFactory.createSet(); + for (V s : tuplesForward.distinctKeys()) { + nodes.add(s); + } + return nodes; + } + + /** + * Returns true if a (source, target) node is present in the transitive closure relation, false otherwise. + * + * @param source + * the source node + * @param target + * the target node + * @return true if tuple is present, false otherwise + */ + public boolean containsTuple(V source, V target) { + return tuplesForward.lookupOrEmpty(source).containsNonZero(target); + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj == null || this.getClass() != obj.getClass()) { + return false; + } else { + CountingTcRelation aTR = (CountingTcRelation) obj; + + return tuplesForward.equals(aTR.tuplesForward); + } + } + + @Override + public int hashCode() { + return tuplesForward.hashCode(); + } + + public static CountingTcRelation createFrom(IBiDirectionalGraphDataSource gds) { + List topologicalSorting = TopologicalSorting.compute(gds); + CountingTcRelation tc = new CountingTcRelation(true); + Collections.reverse(topologicalSorting); + for (V n : topologicalSorting) { + IMemoryView sourceNodes = gds.getSourceNodes(n); + Set tupEnds = tc.getTupleEnds(n); + for (V s : sourceNodes.distinctValues()) { + int count = sourceNodes.getCount(s); + for (int i = 0; i < count; i++) { + tc.updateTuple(s, n, true); + if (tupEnds != null) { + for (V t : tupEnds) { + tc.updateTuple(s, t, true); + } + } + } + } + } + + return tc; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/CountingListener.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/CountingListener.java new file mode 100644 index 00000000..ca5c4769 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/CountingListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.incscc; + +import tools.refinery.interpreter.rete.itc.igraph.ITcObserver; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * @author Tamas Szabo + * + */ +public class CountingListener implements ITcObserver { + + private IncSCCAlg alg; + + public CountingListener(IncSCCAlg alg) { + this.alg = alg; + } + + @Override + public void tupleInserted(V source, V target) { + alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.INSERT); + } + + @Override + public void tupleDeleted(V source, V target) { + alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.DELETE); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/IncSCCAlg.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/IncSCCAlg.java new file mode 100644 index 00000000..6956b401 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/incscc/IncSCCAlg.java @@ -0,0 +1,609 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.incscc; + +import tools.refinery.interpreter.matchers.algorithms.UnionFind; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.rete.itc.alg.counting.CountingAlg; +import tools.refinery.interpreter.rete.itc.alg.misc.DFSPathFinder; +import tools.refinery.interpreter.rete.itc.alg.misc.GraphHelper; +import tools.refinery.interpreter.rete.itc.alg.misc.IGraphPathFinder; +import tools.refinery.interpreter.rete.itc.alg.misc.Tuple; +import tools.refinery.interpreter.rete.itc.alg.misc.bfs.BFS; +import tools.refinery.interpreter.rete.itc.alg.misc.scc.SCC; +import tools.refinery.interpreter.rete.itc.alg.misc.scc.SCCResult; +import tools.refinery.interpreter.rete.itc.alg.util.CollectionHelper; +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.itc.igraph.*; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Incremental SCC maintenance + counting algorithm. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class IncSCCAlg implements IGraphObserver, ITcDataSource { + + public UnionFind sccs; + public IBiDirectionalGraphDataSource gds; + private CountingAlg counting; + private Graph reducedGraph; + private IBiDirectionalGraphDataSource reducedGraphIndexer; + private List> observers; + private CountingListener countingListener; + + public IncSCCAlg(IGraphDataSource graphDataSource) { + + if (graphDataSource instanceof IBiDirectionalGraphDataSource) { + gds = (IBiDirectionalGraphDataSource) graphDataSource; + } else { + gds = new IBiDirectionalWrapper(graphDataSource); + } + observers = CollectionsFactory.createObserverList(); + sccs = new UnionFind(); + reducedGraph = new Graph(); + reducedGraphIndexer = new IBiDirectionalWrapper(reducedGraph); + countingListener = new CountingListener(this); + initalizeInternalDataStructures(); + gds.attachObserver(this); + } + + private void initalizeInternalDataStructures() { + SCCResult _sccres = SCC.computeSCC(gds); + Set> _sccs = _sccres.getSccs(); + + for (Set _set : _sccs) { + sccs.makeSet(_set); + } + + // Initalization of the reduced graph + for (V n : sccs.getPartitionHeads()) { + reducedGraph.insertNode(n); + } + + for (V source : gds.getAllNodes()) { + final IMemoryView targetNodes = gds.getTargetNodes(source); + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V target = entry.getKey(); + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (!sourceRoot.equals(targetRoot)) { + reducedGraph.insertEdge(sourceRoot, targetRoot); + } + } + } + } + + counting = new CountingAlg(reducedGraph); + } + + @Override + public void edgeInserted(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + // Different SCC + if (!sourceRoot.equals(targetRoot)) { + + // source is reachable from target? + if (counting.isReachable(targetRoot, sourceRoot)) { + + Set predecessorRoots = counting.getAllReachableSources(sourceRoot); + Set successorRoots = counting.getAllReachableTargets(targetRoot); + + // 1. intersection of source and target roots, these will be in the merged SCC + Set isectRoots = CollectionHelper.intersection(predecessorRoots, successorRoots); + isectRoots.add(sourceRoot); + isectRoots.add(targetRoot); + + // notifications must be issued before Union-Find modifications + if (observers.size() > 0) { + Set sourceSCCs = createSetNullTolerant(predecessorRoots); + sourceSCCs.add(sourceRoot); + Set targetSCCs = createSetNullTolerant(successorRoots); + targetSCCs.add(targetRoot); + + // tracing back to actual nodes + for (V sourceSCC : sourceSCCs) { + targetLoop: for (V targetSCC : targetSCCs) { + if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop; + + boolean needsNotification = + // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc. + // Issue notifications only if there is no self-loop present at the moment + (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper + .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0) + || + // Case 2. sourceSCC and targetSCC are different sccs. + (!sourceSCC.equals(targetSCC)); + // if self loop is already present omit the notification + if (needsNotification) { + notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC), + Direction.INSERT); + } + } + } + } + + // 2. delete edges, nodes + List sourceSCCs = new ArrayList(); + List targetSCCs = new ArrayList(); + + for (V r : isectRoots) { + List sourceSCCsOfSCC = getSourceSCCsOfSCC(r); + List targetSCCsOfSCC = getTargetSCCsOfSCC(r); + + for (V sourceSCC : sourceSCCsOfSCC) { + if (!sourceSCC.equals(r)) { + reducedGraph.deleteEdgeIfExists(sourceSCC, r); + } + } + + for (V targetSCC : targetSCCsOfSCC) { + if (!isectRoots.contains(targetSCC) && !r.equals(targetSCC)) { + reducedGraph.deleteEdgeIfExists(r, targetSCC); + } + } + + sourceSCCs.addAll(sourceSCCsOfSCC); + targetSCCs.addAll(targetSCCsOfSCC); + } + + for (V r : isectRoots) { + reducedGraph.deleteNode(r); + } + + // 3. union + Iterator iterator = isectRoots.iterator(); + V newRoot = iterator.next(); + while (iterator.hasNext()) { + newRoot = sccs.union(newRoot, iterator.next()); + } + + // 4. add new node + reducedGraph.insertNode(newRoot); + + // 5. add edges + Set containedNodes = sccs.getPartition(newRoot); + + for (V sourceSCC : sourceSCCs) { + if (!containedNodes.contains(sourceSCC) && !sourceSCC.equals(newRoot)) { + reducedGraph.insertEdge(sourceSCC, newRoot); + } + } + for (V targetSCC : targetSCCs) { + if (!containedNodes.contains(targetSCC) && !targetSCC.equals(newRoot)) { + reducedGraph.insertEdge(newRoot, targetSCC); + } + } + } else { + if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 1) { + counting.attachObserver(countingListener); + } + reducedGraph.insertEdge(sourceRoot, targetRoot); + counting.detachObserver(countingListener); + } + } else { + // Notifications about self-loops + if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1 + && GraphHelper.getEdgeCount(source, target, gds) == 1) { + notifyTcObservers(source, source, Direction.INSERT); + } + } + } + + @Override + public void edgeDeleted(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (!sourceRoot.equals(targetRoot)) { + if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 0) { + counting.attachObserver(countingListener); + } + reducedGraph.deleteEdgeIfExists(sourceRoot, targetRoot); + counting.detachObserver(countingListener); + } else { + // get the graph for the scc whose root is sourceRoot + Graph g = GraphHelper.getSubGraph(sccs.getPartition(sourceRoot), gds); + + // if source is not reachable from target anymore + if (!BFS.isReachable(source, target, g)) { + // create copies of the current state before destructive manipulation + Map reachableSources = CollectionsFactory.createMap(); + for (Entry entry : reducedGraphIndexer.getSourceNodes(sourceRoot).entriesWithMultiplicities()) { + reachableSources.put(entry.getKey(), entry.getValue()); + } + Map reachableTargets = CollectionsFactory.createMap(); + for (Entry entry : reducedGraphIndexer.getTargetNodes(sourceRoot).entriesWithMultiplicities()) { + reachableTargets.put(entry.getKey(), entry.getValue()); + } + + SCCResult _newSccs = SCC.computeSCC(g); + + // delete scc node (and with its edges too) + for (Entry entry : reachableSources.entrySet()) { + V s = entry.getKey(); + for (int i = 0; i < entry.getValue(); i++) { + reducedGraph.deleteEdgeIfExists(s, sourceRoot); + } + } + + for (Entry entry : reachableTargets.entrySet()) { + V t = entry.getKey(); + for (int i = 0; i < entry.getValue(); i++) { + reducedGraph.deleteEdgeIfExists(sourceRoot, t); + } + } + + sccs.deleteSet(sourceRoot); + reducedGraph.deleteNode(sourceRoot); + + Set> newSCCs = _newSccs.getSccs(); + Set newSCCRoots = CollectionsFactory.createSet(); + + // add new nodes and edges to the reduced graph + for (Set newSCC : newSCCs) { + V newRoot = sccs.makeSet(newSCC); + reducedGraph.insertNode(newRoot); + newSCCRoots.add(newRoot); + } + for (V newSCCRoot : newSCCRoots) { + List sourceSCCsOfSCC = getSourceSCCsOfSCC(newSCCRoot); + List targetSCCsOfSCC = getTargetSCCsOfSCC(newSCCRoot); + + for (V sourceSCC : sourceSCCsOfSCC) { + if (!sourceSCC.equals(newSCCRoot)) { + reducedGraph.insertEdge(sccs.find(sourceSCC), newSCCRoot); + } + } + for (V targetSCC : targetSCCsOfSCC) { + if (!newSCCRoots.contains(targetSCC) && !targetSCC.equals(newSCCRoot)) + reducedGraph.insertEdge(newSCCRoot, targetSCC); + } + } + + // Must be after the union-find modifications + if (observers.size() > 0) { + V newSourceRoot = sccs.find(source); + V newTargetRoot = sccs.find(target); + + Set sourceSCCs = createSetNullTolerant(counting.getAllReachableSources(newSourceRoot)); + sourceSCCs.add(newSourceRoot); + + Set targetSCCs = createSetNullTolerant(counting.getAllReachableTargets(newTargetRoot)); + targetSCCs.add(newTargetRoot); + + for (V sourceSCC : sourceSCCs) { + targetLoop: for (V targetSCC : targetSCCs) { + if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop; + + boolean needsNotification = + // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc. + // Issue notifications only if there is no self-loop present at the moment + (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper + .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0) + || + // Case 2. sourceSCC and targetSCC are different sccs. + (!sourceSCC.equals(targetSCC)); + // if self loop is already present omit the notification + if (needsNotification) { + notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC), + Direction.DELETE); + } + } + } + } + } else { + // only handle self-loop notifications - sourceRoot equals to targetRoot + if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1 + && GraphHelper.getEdgeCount(source, target, gds) == 0) { + notifyTcObservers(source, source, Direction.DELETE); + } + } + } + } + + @Override + public void nodeInserted(V n) { + sccs.makeSet(n); + reducedGraph.insertNode(n); + } + + @Override + public void nodeDeleted(V n) { + IMemoryView sources = gds.getSourceNodes(n); + IMemoryView targets = gds.getTargetNodes(n); + + for (Entry entry : sources.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V source = entry.getKey(); + edgeDeleted(source, n); + } + } + + for (Entry entry : targets.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V target = entry.getKey(); + edgeDeleted(n, target); + } + } + + sccs.deleteSet(n); + } + + @Override + public void attachObserver(ITcObserver to) { + observers.add(to); + } + + @Override + public void detachObserver(ITcObserver to) { + observers.remove(to); + } + + @Override + public Set getAllReachableTargets(V source) { + V sourceRoot = sccs.find(source); + Set containedNodes = sccs.getPartition(sourceRoot); + Set targets = CollectionsFactory.createSet(); + + if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(source, gds) == 1) { + targets.addAll(containedNodes); + } + + Set rootSet = counting.getAllReachableTargets(sourceRoot); + if (rootSet != null) { + for (V _root : rootSet) { + targets.addAll(sccs.getPartition(_root)); + } + } + + return targets; + } + + @Override + public Set getAllReachableSources(V target) { + V targetRoot = sccs.find(target); + Set containedNodes = sccs.getPartition(targetRoot); + Set sources = CollectionsFactory.createSet(); + + if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(target, gds) == 1) { + sources.addAll(containedNodes); + } + + Set rootSet = counting.getAllReachableSources(targetRoot); + if (rootSet != null) { + for (V _root : rootSet) { + sources.addAll(sccs.getPartition(_root)); + } + } + return sources; + } + + @Override + public boolean isReachable(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (sourceRoot.equals(targetRoot)) + return true; + else + return counting.isReachable(sourceRoot, targetRoot); + } + + public List getReachabilityPath(V source, V target) { + if (!isReachable(source, target)) { + return null; + } else { + Set sccsInSubGraph = CollectionHelper.intersection(counting.getAllReachableTargets(source), + counting.getAllReachableSources(target)); + sccsInSubGraph.add(sccs.find(source)); + sccsInSubGraph.add(sccs.find(target)); + Set nodesInSubGraph = CollectionsFactory.createSet(); + + for (V sccRoot : sccsInSubGraph) { + nodesInSubGraph.addAll(sccs.getPartition(sccRoot)); + } + + return GraphHelper.constructPath(source, target, nodesInSubGraph, gds); + } + } + + /** + * Return the SCCs from which the SCC represented by the root node is reachable. Note that an SCC can be present + * multiple times in the returned list (multiple edges between the two SCCs). + * + * @param root + * @return the list of reachable target SCCs + */ + private List getSourceSCCsOfSCC(V root) { + List sourceSCCs = new ArrayList(); + + for (V containedNode : this.sccs.getPartition(root)) { + IMemoryView sourceNodes = this.gds.getSourceNodes(containedNode); + for (V source : sourceNodes.distinctValues()) { + sourceSCCs.add(this.sccs.find(source)); + } + } + + return sourceSCCs; + } + + /** + * Returns true if the SCC represented by the given root node has incoming edges in the reduced graph, + * false otherwise (if this SCC is a source in the reduced graph). + * + * @param root the root node of an SCC + * @return true if it has incoming edges, false otherwise + * @since 1.6 + */ + public boolean hasIncomingEdges(final V root) { + for (final V containedNode : this.sccs.getPartition(root)) { + final IMemoryView sourceNodes = this.gds.getSourceNodes(containedNode); + for (final V source : sourceNodes.distinctValues()) { + final V otherRoot = this.sccs.find(source); + if (!Objects.equals(root, otherRoot)) { + return true; + } + } + } + return false; + } + + /** + * Return the SCCs which are reachable from the SCC represented by the root node. Note that an SCC can be present + * multiple times in the returned list (multiple edges between the two SCCs). + * + * @param root + * @return the list of reachable target SCCs + */ + private List getTargetSCCsOfSCC(V root) { + List targetSCCs = new ArrayList(); + + for (V containedNode : this.sccs.getPartition(root)) { + IMemoryView targetNodes = this.gds.getTargetNodes(containedNode); + for (V target : targetNodes.distinctValues()) { + targetSCCs.add(this.sccs.find(target)); + } + } + + return targetSCCs; + } + + /** + * Returns true if the SCC represented by the given root node has outgoing edges in the reduced graph, + * false otherwise (if this SCC is a sink in the reduced graph). + * + * @param root the root node of an SCC + * @return true if it has outgoing edges, false otherwise + * @since 1.6 + */ + public boolean hasOutgoingEdges(V root) { + for (final V containedNode : this.sccs.getPartition(root)) { + final IMemoryView targetNodes = this.gds.getTargetNodes(containedNode); + for (final V target : targetNodes.distinctValues()) { + final V otherRoot = this.sccs.find(target); + if (!Objects.equals(root, otherRoot)) { + return true; + } + } + } + return false; + } + + @Override + public void dispose() { + gds.detachObserver(this); + counting.dispose(); + } + + /** + * Call this method to notify the observers of the transitive closure relation. The tuples used in the notification + * will be the Descartes product of the two sets given. + * + * @param sources + * the source nodes + * @param targets + * the target nodes + * @param direction + */ + protected void notifyTcObservers(Set sources, Set targets, Direction direction) { + for (V s : sources) { + for (V t : targets) { + notifyTcObservers(s, t, direction); + } + } + } + + private void notifyTcObservers(V source, V target, Direction direction) { + for (ITcObserver observer : observers) { + if (direction == Direction.INSERT) { + observer.tupleInserted(source, target); + } + if (direction == Direction.DELETE) { + observer.tupleDeleted(source, target); + } + } + } + + /** + * Returns the node that is selected as the representative of the SCC containing the argument. + * @since 1.6 + */ + public V getRepresentative(V node) { + return sccs.find(node); + } + + public Set> getTcRelation() { + Set> resultSet = new HashSet>(); + + for (V sourceRoot : sccs.getPartitionHeads()) { + Set sources = sccs.getPartition(sourceRoot); + if (sources.size() > 1 || GraphHelper.getEdgeCount(sources.iterator().next(), gds) == 1) { + for (V source : sources) { + for (V target : sources) { + resultSet.add(new Tuple(source, target)); + } + } + } + + Set reachableTargets = counting.getAllReachableTargets(sourceRoot); + if (reachableTargets != null) { + for (V targetRoot : reachableTargets) { + for (V source : sources) { + for (V target : sccs.getPartition(targetRoot)) { + resultSet.add(new Tuple(source, target)); + } + } + } + } + } + + return resultSet; + } + + public boolean isIsolated(V node) { + IMemoryView targets = gds.getTargetNodes(node); + IMemoryView sources = gds.getSourceNodes(node); + return targets.isEmpty() && sources.isEmpty(); + } + + @Override + public IGraphPathFinder getPathFinder() { + return new DFSPathFinder(gds, this); + } + + /** + * The graph of SCCs; each SCC is represented by its representative node (see {@link #getRepresentative(Object)}) + * @since 1.6 + */ + public Graph getReducedGraph() { + return reducedGraph; + } + + private static Set createSetNullTolerant(Set initial) { + if (initial != null) + return CollectionsFactory.createSet(initial); + else + return CollectionsFactory.createSet(); + } + + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/DFSPathFinder.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/DFSPathFinder.java new file mode 100644 index 00000000..ea94653e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/DFSPathFinder.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus and 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.interpreter.rete.itc.alg.misc; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.ITcDataSource; +import tools.refinery.interpreter.matchers.util.IMemoryView; + +/** + * A depth-first search implementation of the {@link IGraphPathFinder}. + * + * TODO use ITC to filter nodes that must be traversed, instead of checks + * + * @author Abel Hegedus + * + * @param + * the node type of the graph + */ +public class DFSPathFinder implements IGraphPathFinder { + + private IGraphDataSource graph; + private ITcDataSource itc; + + public DFSPathFinder(IGraphDataSource graph, ITcDataSource itc) { + this.graph = graph; + this.itc = itc; + } + + @Override + public Iterable> getAllPaths(V sourceNode, V targetNode) { + Set endNodes = new HashSet(); + endNodes.add(targetNode); + return getAllPathsToTargets(sourceNode, endNodes); + } + + @Override + public Iterable> getAllPathsToTargets(V sourceNode, Set targetNodes) { + List> paths = new ArrayList>(); + Deque visited = new LinkedList(); + Set reachableTargets = new HashSet(); + for (V targetNode : targetNodes) { + if (itc.isReachable(sourceNode, targetNode)) { + reachableTargets.add(targetNode); + } + } + if (!reachableTargets.isEmpty()) { + return paths; + } + visited.add(sourceNode); + return getPaths(paths, visited, reachableTargets); + } + + protected Iterable> getPaths(List> paths, Deque visited, Set targetNodes) { + IMemoryView nodes = graph.getTargetNodes(visited.getLast()); + // examine adjacent nodes + for (V node : nodes.distinctValues()) { + if (visited.contains(node)) { + continue; + } + if (targetNodes.contains(node)) { + visited.add(node); + // clone visited LinkedList + Deque visitedClone = new LinkedList(visited); + paths.add(visitedClone); + visited.removeLast(); + break; + } + } + + // in breadth-first, recursion needs to come after visiting connected nodes + for (V node : nodes.distinctValues()) { + if (visited.contains(node) || targetNodes.contains(node)) { + continue; + } + boolean canReachTarget = false; + for (V target : targetNodes) { + if (itc.isReachable(node, target)) { + canReachTarget = true; + break; + } + } + if (canReachTarget) { + visited.addLast(node); + getPaths(paths, visited, targetNodes); + visited.removeLast(); + } + } + + return paths; + } + + public String printPaths(List> paths) { + StringBuilder sb = new StringBuilder(); + for (Deque visited : paths) { + sb.append("Path: "); + for (V node : visited) { + sb.append(node); + sb.append(" --> "); + } + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public Deque getPath(V sourceNode, V targetNode) { + // TODO optimize + Iterable> allPaths = getAllPaths(sourceNode, targetNode); + Iterator> pathIterator = allPaths.iterator(); + return pathIterator.hasNext() ? pathIterator.next() : new LinkedList(); + } + + @Override + public Iterable> getShortestPaths(V sourceNode, V targetNode) { + // TODO optimize + Iterable> allPaths = getAllPaths(sourceNode, targetNode); + List> shortestPaths = new ArrayList>(); + int shortestPathLength = -1; + for (Deque path : allPaths) { + int pathLength = path.size(); + if (shortestPathLength == -1 || pathLength < shortestPathLength) { + shortestPaths.clear(); + shortestPathLength = pathLength; + } + if (pathLength == shortestPathLength) { + shortestPaths.add(path); + } + } + return shortestPaths; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Edge.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Edge.java new file mode 100644 index 00000000..98646340 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Edge.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc; + +public class Edge { + private V source; + private V target; + + public Edge(V source, V target) { + super(); + this.source = source; + this.target = target; + } + + public V getSource() { + return source; + } + + public void setSource(V source) { + this.source = source; + } + + public V getTarget() { + return target; + } + + public void setTarget(V target) { + this.target = target; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/GraphHelper.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/GraphHelper.java new file mode 100644 index 00000000..9d1c3bcd --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/GraphHelper.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, 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.interpreter.rete.itc.alg.misc; + +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.matchers.util.IMemoryView; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Utility class for graph related operations. + * + * @author Tamas Szabo + */ +public class GraphHelper { + + private GraphHelper() {/*Utility class constructor*/} + + /** + * Returns the subgraph from the given {@link IBiDirectionalGraphDataSource} which contains the given set of nodes. + * + * @param nodesInSubGraph + * the nodes that are present in the subgraph + * @param graphDataSource + * the graph data source for the original graph + * @return the subgraph associated to the given nodes + */ + public static Graph getSubGraph(Collection nodesInSubGraph, + IBiDirectionalGraphDataSource graphDataSource) { + Graph g = new Graph(); + if (nodesInSubGraph != null) { + for (V node : nodesInSubGraph) { + g.insertNode(node); + } + + for (V node : nodesInSubGraph) { + IMemoryView sources = graphDataSource.getSourceNodes(node); + for (Entry entry : sources.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V s = entry.getKey(); + if (nodesInSubGraph.contains(s)) { + g.insertEdge(s, node); + } + } + } + } + } + + return g; + } + + /** + * Constructs a path between source and target in the given graph. Both the {@link IGraphDataSource} and the set of + * nodes are used, this way it is possible to construct a path in a given subgraph. + * + * The returned {@link List} contains the nodes along the path (this means that there is an edge in the graph + * between two consecutive nodes). A self loop (one edge) is indicated with the source node being present two times + * in the returned {@link List}. + * + * @param source + * the source node + * @param target + * the target node + * @param nodesInGraph + * the nodes that are present in the subgraph + * @param graphDataSource + * the graph data source + * @return the path between the two nodes + */ + public static List constructPath(V source, V target, Set nodesInGraph, + IGraphDataSource graphDataSource) { + Set visitedNodes = new HashSet(); + List path = new ArrayList(); + + visitedNodes.add(source); + path.add(source); + V act = source; + + // if source and target are the same node + if (source.equals(target) && graphDataSource.getTargetNodes(source).containsNonZero(target)) { + // the node will be present in the path two times + path.add(source); + return path; + } else { + while (act != null) { + V nextNode = getNextNodeToVisit(act, graphDataSource, nodesInGraph, visitedNodes); + if (nextNode == null && path.size() > 1) { + // needs to backtrack along path + // remove the last element in the path because we can't go + // anywhere from there + path.remove(path.size() - 1); + while (nextNode == null && path.size() > 0) { + V lastPathElement = path.get(path.size() - 1); + nextNode = getNextNodeToVisit(lastPathElement, graphDataSource, nodesInGraph, visitedNodes); + if (nextNode == null) { + path.remove(path.size() - 1); + } + } + } + + if (nextNode != null) { + visitedNodes.add(nextNode); + path.add(nextNode); + if (nextNode.equals(target)) { + return path; + } + } + act = nextNode; + } + return null; + } + } + + private static V getNextNodeToVisit(V act, IGraphDataSource graphDataSource, Set nodesInSubGraph, + Set visitedNodes) { + IMemoryView targetNodes = graphDataSource.getTargetNodes(act); + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V node = entry.getKey(); + if (nodesInSubGraph.contains(node) && !visitedNodes.contains(node)) { + return node; + } + } + } + return null; + } + + /** + * Returns the number of self-loop edges for the given node. + * + * @param node + * the node + * @param graphDataSource + * the graph data source + * @return the number of self-loop edges + */ + public static int getEdgeCount(V node, IGraphDataSource graphDataSource) { + return getEdgeCount(node, node, graphDataSource); + } + + /** + * Returns the number of edges between the given source and target nodes. + * + * @param source + * the source node + * @param target + * the target node + * @param graphDataSource + * the graph data source + * @return the number of parallel edges between the two nodes + */ + public static int getEdgeCount(V source, V target, IGraphDataSource graphDataSource) { + Integer count = graphDataSource.getTargetNodes(source).getCount(target); + if (count == null) { + return 0; + } else { + return count; + } + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/IGraphPathFinder.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/IGraphPathFinder.java new file mode 100644 index 00000000..6f1544df --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/IGraphPathFinder.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, 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.interpreter.rete.itc.alg.misc; + +import java.util.Deque; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.igraph.ITcDataSource; + +/** + * The path finder provides methods for retrieving paths in a graph between a source node and one or more target nodes. + * Use {@link ITcDataSource#getPathFinder()} for instantiating. + * + * @author Abel Hegedus + * + * @param the node type of the graph + */ +public interface IGraphPathFinder { + + /** + * Returns an arbitrary path from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the path from the source to the target, or empty collection if target is not reachable from source. + */ + Deque getPath(V sourceNode, V targetNode); + + /** + * Returns the collection of shortest paths from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the collection of shortest paths from the source to the target, or empty collection if target is not reachable from source. + */ + Iterable> getShortestPaths(V sourceNode, V targetNode); + + /** + * Returns the collection of paths from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the collection of paths from the source to the target, or empty collection if target is not reachable from source. + */ + Iterable> getAllPaths(V sourceNode, V targetNode); + + /** + * Returns the collection of paths from the source node to any of the target nodes (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNodes the set of target nodes of the paths + * @return the collection of paths from the source to any of the targets, or empty collection if neither target is reachable from source. + */ + Iterable> getAllPathsToTargets(V sourceNode, Set targetNodes); + + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/ITcRelation.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/ITcRelation.java new file mode 100644 index 00000000..5b0c8d98 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/ITcRelation.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc; + +import java.util.Set; + +public interface ITcRelation { + + /** + * Returns the starting nodes from a transitive closure relation. + * + * @return the set of starting nodes + */ + public Set getTupleStarts(); + + /** + * Returns the set of nodes that are reachable from the given node. + * + * @param start + * the starting node + * @return the set of reachable nodes + */ + public Set getTupleEnds(V start); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Tuple.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Tuple.java new file mode 100644 index 00000000..0a74f932 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/Tuple.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc; + +public class Tuple { + + private V source; + private V target; + + public Tuple(V source, V target) { + super(); + this.source = source; + this.target = target; + } + + public V getSource() { + return source; + } + + public void setSource(V source) { + this.source = source; + } + + public V getTarget() { + return target; + } + + public void setTarget(V target) { + this.target = target; + } + + @Override + public String toString() { + return "(" + source.toString() + "," + target.toString() + ")"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Tuple) { + Tuple t = (Tuple) o; + + if (t.getSource().equals(this.source) && t.getTarget().equals(this.target)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return source.hashCode() + target.hashCode(); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/bfs/BFS.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/bfs/BFS.java new file mode 100644 index 00000000..ecd164c0 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/bfs/BFS.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.bfs; + +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; + +import java.util.*; + +public class BFS { + + private BFS() {/*Utility class constructor*/} + + /** + * Performs a breadth first search on the given graph to determine whether source is reachable from target. + * + * @param + * the type parameter of the nodes in the graph + * @param source + * the source node + * @param target + * the target node + * @param graph + * the graph data source + * @return true if source is reachable from target, false otherwise + */ + public static boolean isReachable(V source, V target, IGraphDataSource graph) { + Deque nodeQueue = new ArrayDeque(); + Set visited = new HashSet(); + + nodeQueue.add(source); + visited.add(source); + + boolean ret = _isReachable(target, graph, nodeQueue, visited); + return ret; + } + + private static boolean _isReachable(V target, IGraphDataSource graph, Deque nodeQueue, Set visited) { + + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.removeFirst(); + for (V t : graph.getTargetNodes(node).distinctValues()){ + if (t.equals(target)) { + return true; + } + if (!visited.contains(t)) { + visited.add(t); + nodeQueue.addLast(t); + } + } + } + return false; + } + + public static Set reachableSources(IBiDirectionalGraphDataSource graph, V target) { + Set retSet = new HashSet(); + retSet.add(target); + Deque nodeQueue = new ArrayDeque(); + nodeQueue.add(target); + + _reachableSources(graph, nodeQueue, retSet); + + return retSet; + } + + private static void _reachableSources(IBiDirectionalGraphDataSource graph, Deque nodeQueue, + Set retSet) { + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.removeFirst(); + for (V _node : graph.getSourceNodes(node).distinctValues()) { + if (!retSet.contains(_node)) { + retSet.add(_node); + nodeQueue.addLast(_node); + } + } + } + } + + public static Set reachableTargets(IGraphDataSource graph, V source) { + Set retSet = new HashSet(); + retSet.add(source); + Deque nodeQueue = new ArrayDeque(); + nodeQueue.add(source); + + _reachableTargets(graph, nodeQueue, retSet); + + return retSet; + } + + private static void _reachableTargets(IGraphDataSource graph, Deque nodeQueue, Set retSet) { + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.removeFirst(); + + for (V _node : graph.getTargetNodes(node).distinctValues()) { + + if (!retSet.contains(_node)) { + retSet.add(_node); + nodeQueue.addLast(_node); + } + } + } + } + + /** + * Performs a breadth first search on the given graph and collects all the nodes along the path from source to + * target if such path exists. + * + * @param + * the type parameter of the nodes in the graph + * @param source + * the source node + * @param target + * the target node + * @param graph + * the graph data source + * @return the set of nodes along the path + */ + public static Set collectNodesAlongPath(V source, V target, IGraphDataSource graph) { + Set path = new HashSet(); + _collectNodesAlongPath(source, target, graph, path); + return path; + } + + private static boolean _collectNodesAlongPath(V node, V target, IGraphDataSource graph, Set path) { + + boolean res = false; + + // end recursion + if (node.equals(target)) { + path.add(node); + return true; + } else { + for (V _nodeT : graph.getTargetNodes(node).distinctValues()) { + res = (_collectNodesAlongPath(_nodeT, target, graph, path)) || res; + } + if (res) + path.add(node); + return res; + } + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/PKAlg.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/PKAlg.java new file mode 100644 index 00000000..0aaf8a0d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/PKAlg.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.scc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IGraphObserver; + +public class PKAlg implements IGraphObserver { + + /** + * Maps the nodes to their indicies. + */ + private Map node2index; + private Map index2node; + private Map node2mark; + + /** + * Maps the index of a node to the index in the topsort. + */ + private Map index2topsort; + private Map topsort2index; + + /** + * Index associated to the inserted nodes (incrementing with every insertion). + */ + private int index; + + /** + * Index within the topsort for the target node when edge insertion occurs. + */ + private int lower_bound; + + /** + * Index within the topsort for the source node when edge insertion occurs. + */ + private int upper_bound; + + private List RF; + private List RB; + private IBiDirectionalGraphDataSource gds; + + public PKAlg(IGraphDataSource gds) { + if (gds instanceof IBiDirectionalGraphDataSource) { + this.gds = (IBiDirectionalGraphDataSource) gds; + } else { + this.gds = new IBiDirectionalWrapper(gds); + } + + node2mark = new HashMap(); + node2index = new HashMap(); + index2node = new HashMap(); + index2topsort = new HashMap(); + topsort2index = new HashMap(); + index = 0; + + gds.attachObserver(this); + } + + @Override + public void edgeInserted(V source, V target) { + + RF = new ArrayList(); + RB = new ArrayList(); + + lower_bound = index2topsort.get(node2index.get(target)); + upper_bound = index2topsort.get(node2index.get(source)); + + if (lower_bound < upper_bound) { + dfsForward(target); + dfsBackward(source); + reorder(); + } + } + + private List getIndicies(List list) { + List indicies = new ArrayList(); + + for (V n : list) + indicies.add(index2topsort.get(node2index.get(n))); + + return indicies; + } + + private void reorder() { + + Collections.reverse(RB); + + // azon csomopontok indexei amelyek sorrendje nem jo + List L = getIndicies(RF); + L.addAll(getIndicies(RB)); + Collections.sort(L); + + for (int i = 0; i < RB.size(); i++) { + index2topsort.put(node2index.get(RB.get(i)), L.get(i)); + topsort2index.put(L.get(i), node2index.get(RB.get(i))); + } + + for (int i = 0; i < RF.size(); i++) { + index2topsort.put(node2index.get(RF.get(i)), L.get(i + RB.size())); + topsort2index.put(L.get(i + RB.size()), node2index.get(RF.get(i))); + } + } + + @SuppressWarnings("unused") + private List getTopSort() { + List topsort = new ArrayList(); + + for (int i : topsort2index.values()) { + topsort.add(index2node.get(i)); + } + + return topsort; + } + + private void dfsBackward(V node) { + node2mark.put(node, true); + RB.add(node); + + for (V sn : gds.getSourceNodes(node).distinctValues()) { + int top_id = index2topsort.get(node2index.get(sn)); + + if (!node2mark.get(sn) && lower_bound < top_id) + dfsBackward(sn); + } + } + + private void dfsForward(V node) { + node2mark.put(node, true); + RF.add(node); + + for (V tn : gds.getTargetNodes(node).distinctValues()) { + int top_id = index2topsort.get(node2index.get(tn)); + + if (top_id == upper_bound) + System.out.println("!!!Cycle detected!!!"); + else if (!node2mark.get(tn) && top_id < upper_bound) + dfsForward(tn); + } + } + + @Override + public void edgeDeleted(V source, V target) { + // Edge deletion does not affect topsort + } + + @Override + public void nodeInserted(V n) { + node2mark.put(n, false); + node2index.put(n, index); + index2node.put(index, n); + index2topsort.put(index, index); + topsort2index.put(index, index); + index++; + } + + @Override + public void nodeDeleted(V n) { + node2mark.remove(n); + int node_id = node2index.remove(n); + index2node.remove(node_id); + int top_id = index2topsort.remove(node_id); + topsort2index.remove(top_id); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCC.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCC.java new file mode 100644 index 00000000..da145fb1 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCC.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.scc; + +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; + +import java.util.*; + +/** + * Efficient algorithms to compute the Strongly Connected Components in a directed graph. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph + */ +public class SCC { + + private SCC() {/*Utility class constructor*/} + + public static long sccId = 0; + + /** + * Computes the SCCs for the given graph and returns them as a multiset. (Iterative version of Tarjan's algorithm) + * + * @param g + * the directed graph data source + * @return the set of SCCs + */ + public static SCCResult computeSCC(IGraphDataSource g) { + int index = 0; + Set> ret = new HashSet>(); + + // stores the lowlink and index information for the given node + Map nodeMap = CollectionsFactory.createMap(); + + // stores all target nodes of a given node - the list will be modified + Map> targetNodeMap = CollectionsFactory.createMap(); + + // stores those target nodes for a given node which have not been visited + Map> notVisitedMap = CollectionsFactory.createMap(); + + // stores the nodes during the traversal + Deque nodeStack = new ArrayDeque(); + + // stores the nodes which belong to an scc (there can be many sccs in the stack at the same time) + Deque sccStack = new ArrayDeque(); + + boolean sink = false, finishedTraversal = true; + + // initialize all nodes with 0 index and 0 lowlink + Set allNodes = g.getAllNodes(); + for (V n : allNodes) { + nodeMap.put(n, new SCCProperty(0, 0)); + } + + for (V n : allNodes) { + // if the node has not been visited yet + if (nodeMap.get(n).getIndex() == 0) { + nodeStack.push(n); + + while (!nodeStack.isEmpty()) { + V currentNode = nodeStack.peekLast(); + sink = false; + finishedTraversal = false; + SCCProperty prop = nodeMap.get(currentNode); + + if (nodeMap.get(currentNode).getIndex() == 0) { + index++; + sccStack.addLast(currentNode); + prop.setIndex(index); + prop.setLowlink(index); + + notVisitedMap.put(currentNode, new HashSet()); + + // storing the target nodes of the actual node + if (g.getTargetNodes(currentNode) != null) { + Set targets = g.getTargetNodes(currentNode).distinctValues(); + targetNodeMap.put(currentNode, CollectionsFactory.createSet(targets)); + } + } + + if (targetNodeMap.get(currentNode) != null) { + + // remove node from stack, the exploration of its children has finished + if (targetNodeMap.get(currentNode).size() == 0) { + targetNodeMap.remove(currentNode); + + nodeStack.removeLast(); + + for (V targetNode : g.getTargetNodes(currentNode).distinctValues()) { + if (notVisitedMap.get(currentNode).contains(targetNode)) { + prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getLowlink())); + } else if (sccStack.contains(targetNode)) { + prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getIndex())); + } + } + + finishedTraversal = true; + } else { + V targetNode = targetNodeMap.get(currentNode).iterator().next(); + targetNodeMap.get(currentNode).remove(targetNode); + // if the targetNode has not yet been visited push it to the stack + // and mark it in the notVisitedMap + if (nodeMap.get(targetNode).getIndex() == 0) { + notVisitedMap.get(currentNode).add(targetNode); + nodeStack.addLast(targetNode); + } + } + } + // if currentNode has no target nodes + else { + nodeStack.removeLast(); + sink = true; + } + + // create scc if node is a sink or an scc has been found + if ((sink || finishedTraversal) && (prop.getLowlink() == prop.getIndex())) { + Set sc = new HashSet(); + V targetNode = null; + + do { + targetNode = sccStack.removeLast(); + sc.add(targetNode); + } while (!targetNode.equals(currentNode)); + + ret.add(sc); + } + } + } + } + + return new SCCResult(ret, g); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCProperty.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCProperty.java new file mode 100644 index 00000000..727766fe --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCProperty.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.scc; + +public class SCCProperty { + private int index; + private int lowlink; + + public SCCProperty(int index, int lowlink) { + super(); + this.index = index; + this.lowlink = lowlink; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getLowlink() { + return lowlink; + } + + public void setLowlink(int lowlink) { + this.lowlink = lowlink; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCResult.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCResult.java new file mode 100644 index 00000000..51edcca8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/scc/SCCResult.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.scc; + +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; + +public class SCCResult { + + private Set> sccs; + private IGraphDataSource gds; + + public SCCResult(Set> sccs, IGraphDataSource gds) { + this.sccs = sccs; + this.gds = gds; + } + + public Set> getSccs() { + return sccs; + } + + public int getSCCCount() { + return sccs.size(); + } + + public double getAverageNodeCount() { + double a = 0; + + for (Set s : sccs) { + a += s.size(); + } + + return a / sccs.size(); + } + + public double getAverageEdgeCount() { + long edgeSum = 0; + + for (Set scc : sccs) { + for (V source : scc) { + for (Entry entry : gds.getTargetNodes(source).entriesWithMultiplicities()) { + if (scc.contains(entry.getKey())) { + edgeSum += entry.getValue(); + } + } + } + } + + return (double) edgeSum / (double) sccs.size(); + } + + public int getBiggestSCCSize() { + int max = 0; + + for (Set scc : sccs) { + if (scc.size() > max) + max = scc.size(); + } + + return max; + } + + public long getSumOfSquares() { + long sum = 0; + + for (Set scc : sccs) { + sum += scc.size() * scc.size(); + } + + return sum; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/topsort/TopologicalSorting.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/topsort/TopologicalSorting.java new file mode 100644 index 00000000..0a4351f0 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/misc/topsort/TopologicalSorting.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.misc.topsort; + +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; + +import java.util.*; + +/** + * @since 1.6 + */ +public class TopologicalSorting { + + private TopologicalSorting() {/*Utility class constructor*/} + + private static final class Pair { + public T element; + public boolean isParent; + + public Pair(final T element, final boolean isParent) { + this.element = element; + this.isParent = isParent; + } + } + + /** + * Returns a topological ordering for the given graph data source. + * Output format: if there is an a -> b (transitive) reachability, then node a will come before node b in the resulting list. + * + * @param gds the graph data source + * @return a topological ordering + */ + public static List compute(final IGraphDataSource gds) { + final Set visited = new HashSet(); + final LinkedList result = new LinkedList(); + final Deque> dfsStack = new ArrayDeque>(); + + for (final T node : gds.getAllNodes()) { + if (!visited.contains(node)) { + dfsStack.addLast(new Pair(node, false)); + } + + while (!dfsStack.isEmpty()) { + final Pair head = dfsStack.removeLast(); + final T source = head.element; + + if (head.isParent) { + // we have already seen source, push it to the resulting stack + result.addFirst(source); + } else { + // first time we see source, continue with its children + visited.add(source); + dfsStack.addLast(new Pair(source, true)); + + for (final T target : gds.getTargetNodes(source).distinctValues()) { + if (!visited.contains(target)) { + dfsStack.addLast(new Pair(target, false)); + } + } + } + } + } + + return result; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java new file mode 100644 index 00000000..4d3ad759 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeElectionAlgorithm.java @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.rete.itc.alg.representative; + +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.itc.igraph.IGraphObserver; +import tools.refinery.interpreter.matchers.util.Direction; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public abstract class RepresentativeElectionAlgorithm implements IGraphObserver { + protected final Graph graph; + protected final Map representatives = new HashMap<>(); + protected final Map> components = new HashMap<>(); + private RepresentativeObserver observer; + + protected RepresentativeElectionAlgorithm(Graph graph) { + this.graph = graph; + initializeComponents(); + graph.attachObserver(this); + } + + protected abstract void initializeComponents(); + + protected void initializeSet(Set set) { + var iterator = set.iterator(); + if (!iterator.hasNext()) { + // Set is empty. + return; + } + var representative = iterator.next(); + for (var node : set) { + var oldRepresentative = representatives.put(node, representative); + if (oldRepresentative != null && !representative.equals(oldRepresentative)) { + throw new IllegalStateException("Node %s is already in a set represented by %s, cannot add it to %s" + .formatted(node, oldRepresentative, set)); + } + } + components.put(representative, set); + } + + protected void merge(Set toMerge) { + if (toMerge.isEmpty()) { + return; + } + var representativesToMerge = new HashSet<>(); + Object bestRepresentative = null; + Set bestSet = null; + for (var object : toMerge) { + var representative = getRepresentative(object); + if (representativesToMerge.add(representative)) { + var component = getComponent(representative); + if (bestSet == null || bestSet.size() < component.size()) { + bestRepresentative = representative; + bestSet = component; + } + } + } + if (bestRepresentative == null) { + throw new AssertionError("Could not determine best representative"); + } + for (var representative : representativesToMerge) { + if (!bestRepresentative.equals(representative)) { + components.remove(representative); + } + } + components.put(bestRepresentative, toMerge); + for (var object : toMerge) { + var previousRepresentative = representatives.put(object, bestRepresentative); + if (!bestSet.contains(object)) { + notifyToObservers(object, previousRepresentative, bestRepresentative); + } + } + } + + protected void merge(Object leftRepresentative, Object rightRepresentative) { + if (leftRepresentative.equals(rightRepresentative)) { + return; + } + var leftSet = getComponent(leftRepresentative); + var rightSet = getComponent(rightRepresentative); + if (leftSet.size() < rightSet.size()) { + merge(rightRepresentative, rightSet, leftRepresentative, leftSet); + } else { + merge(leftRepresentative, leftSet, rightRepresentative, rightSet); + } + } + + private void merge(Object preservedRepresentative, Set preservedSet, Object removedRepresentative, + Set removedSet) { + components.remove(removedRepresentative); + for (var node : removedSet) { + representatives.put(node, preservedRepresentative); + preservedSet.add(node); + notifyToObservers(node, removedRepresentative, preservedRepresentative); + } + } + + protected void assignNewRepresentative(Object oldRepresentative, Set set) { + var iterator = set.iterator(); + if (!iterator.hasNext()) { + return; + } + var newRepresentative = iterator.next(); + components.put(newRepresentative, set); + for (var node : set) { + var oldRepresentativeOfNode = representatives.put(node, newRepresentative); + if (!oldRepresentative.equals(oldRepresentativeOfNode)) { + throw new IllegalArgumentException("Node %s was not represented by %s but by %s" + .formatted(node, oldRepresentative, oldRepresentativeOfNode)); + } + notifyToObservers(node, oldRepresentative, newRepresentative); + } + } + + public void setObserver(RepresentativeObserver observer) { + this.observer = observer; + } + + public Map> getComponents() { + return components; + } + + public Object getRepresentative(Object node) { + return representatives.get(node); + } + + public Set getComponent(Object representative) { + return components.get(representative); + } + + public void dispose() { + graph.detachObserver(this); + } + + @Override + public void nodeInserted(Object n) { + var component = new HashSet<>(1); + component.add(n); + initializeSet(component); + notifyToObservers(n, n, Direction.INSERT); + } + + @Override + public void nodeDeleted(Object n) { + var representative = representatives.remove(n); + if (!representative.equals(n)) { + throw new IllegalStateException("Trying to delete node with dangling edges"); + } + components.remove(representative); + notifyToObservers(n, representative, Direction.DELETE); + } + + protected void notifyToObservers(Object node, Object oldRepresentative, Object newRepresentative) { + notifyToObservers(node, oldRepresentative, Direction.DELETE); + notifyToObservers(node, newRepresentative, Direction.INSERT); + } + + protected void notifyToObservers(Object node, Object representative, Direction direction) { + if (observer != null) { + observer.tupleChanged(node, representative, direction); + } + } + + public interface Factory { + RepresentativeElectionAlgorithm create(Graph graph); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeObserver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeObserver.java new file mode 100644 index 00000000..7844f1c7 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/RepresentativeObserver.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.rete.itc.alg.representative; + +import tools.refinery.interpreter.matchers.util.Direction; + +public interface RepresentativeObserver { + void tupleChanged(Object node, Object representative, Direction direction); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java new file mode 100644 index 00000000..44afd73e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/StronglyConnectedComponentAlgorithm.java @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.rete.itc.alg.representative; + +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.itc.alg.misc.GraphHelper; +import tools.refinery.interpreter.rete.itc.alg.misc.bfs.BFS; +import tools.refinery.interpreter.rete.itc.alg.misc.scc.SCC; + +import java.util.Collection; +import java.util.Set; + +public class StronglyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { + public StronglyConnectedComponentAlgorithm(Graph graph) { + super(graph); + } + + @Override + protected void initializeComponents() { + var computedSCCs = SCC.computeSCC(graph).getSccs(); + for (var computedSCC : computedSCCs) { + initializeSet(computedSCC); + } + } + + @Override + public void edgeInserted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (sourceRoot.equals(targetRoot)) { + // New edge does not change strongly connected components. + return; + } + if (BFS.isReachable(target, source, graph)) { + var sources = BFS.reachableSources(graph, target); + var targets = BFS.reachableTargets(graph, source); + sources.retainAll(targets); + merge(sources); + } + } + + @Override + public void edgeDeleted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (!sourceRoot.equals(targetRoot)) { + // New edge does not change strongly connected components. + return; + } + var component = GraphHelper.getSubGraph(getComponent(sourceRoot), graph); + if (!BFS.isReachable(source, target, component)) { + var newSCCs = SCC.computeSCC(component).getSccs(); + split(sourceRoot, newSCCs); + } + } + + private void split(Object preservedRepresentative, Collection> sets) { + for (var set : sets) { + if (set.contains(preservedRepresentative)) { + components.put(preservedRepresentative, set); + } else { + assignNewRepresentative(preservedRepresentative, set); + } + } + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java new file mode 100644 index 00000000..58227c44 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.rete.itc.alg.representative; + +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.Set; + +public class WeaklyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { + public WeaklyConnectedComponentAlgorithm(Graph graph) { + super(graph); + } + + @Override + protected void initializeComponents() { + for (var node : graph.getAllNodes()) { + if (representatives.containsKey(node)) { + continue; + } + var reachable = getReachableNodes(node); + initializeSet(reachable); + } + } + + @Override + public void edgeInserted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + merge(sourceRoot, targetRoot); + } + + @Override + public void edgeDeleted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (!sourceRoot.equals(targetRoot)) { + throw new IllegalArgumentException("Trying to remove edge not in graph"); + } + var targetReachable = getReachableNodes(target); + if (!targetReachable.contains(source)) { + split(sourceRoot, targetReachable); + } + } + + private void split(Object sourceRepresentative, Set targetReachable) { + var sourceComponent = getComponent(sourceRepresentative); + sourceComponent.removeAll(targetReachable); + if (targetReachable.contains(sourceRepresentative)) { + components.put(sourceRepresentative, targetReachable); + assignNewRepresentative(sourceRepresentative, sourceComponent); + } else { + assignNewRepresentative(sourceRepresentative, targetReachable); + } + } + + private Set getReachableNodes(Object source) { + var retSet = new HashSet<>(); + retSet.add(source); + var nodeQueue = new ArrayDeque<>(); + nodeQueue.addLast(source); + + while (!nodeQueue.isEmpty()) { + var node = nodeQueue.removeFirst(); + for (var neighbor : graph.getTargetNodes(node).distinctValues()) { + if (!retSet.contains(neighbor)) { + retSet.add(neighbor); + nodeQueue.addLast(neighbor); + } + } + for (var neighbor : graph.getSourceNodes(node).distinctValues()) { + if (!retSet.contains(neighbor)) { + retSet.add(neighbor); + nodeQueue.addLast(neighbor); + } + } + } + + return retSet; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/util/CollectionHelper.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/util/CollectionHelper.java new file mode 100644 index 00000000..4cb227d7 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/alg/util/CollectionHelper.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.alg.util; + +import tools.refinery.interpreter.matchers.util.CollectionsFactory; + +import java.util.Set; + +/** + * @author Tamas Szabo + * + */ +public class CollectionHelper { + + private CollectionHelper() {/*Utility class constructor*/} + + /** + * Returns the intersection of two sets. It calls {@link Set#retainAll(java.util.Collection)} but returns a new set + * containing the elements of the intersection. + * + * @param set1 + * the first set (can be null, interpreted as empty) + * @param set2 + * the second set (can be null, interpreted as empty) + * @return the intersection of the sets + * @since 1.7 + */ + public static Set intersection(Set set1, Set set2) { + if (set1 == null || set2 == null) + return CollectionsFactory.createSet(); + + Set intersection = CollectionsFactory.createSet(set1); + intersection.retainAll(set2); + return intersection; + } + + + /** + * Returns the difference of two sets (S1\S2). It calls {@link Set#removeAll(java.util.Collection)} but returns a + * new set containing the elements of the difference. + * + * @param set1 + * the first set (can be null, interpreted as empty) + * @param set2 + * the second set (can be null, interpreted as empty) + * @return the difference of the sets + * @since 1.7 + */ + public static Set difference(Set set1, Set set2) { + if (set1 == null) + return CollectionsFactory.createSet(); + + Set difference = CollectionsFactory.createSet(set1); + if (set2 != null) difference.removeAll(set2); + return difference; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/DotGenerator.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/DotGenerator.java new file mode 100644 index 00000000..b0998aa9 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/DotGenerator.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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.interpreter.rete.itc.graphimpl; + +import tools.refinery.interpreter.rete.itc.alg.misc.scc.SCC; +import tools.refinery.interpreter.rete.itc.alg.misc.scc.SCCResult; +import tools.refinery.interpreter.matchers.util.IMemoryView; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * This class contains utility methods to generate dot representations for {@link Graph} instances. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class DotGenerator { + + private static final String[] colors = new String[] { "yellow", "blue", "red", "green", "gray", "cyan" }; + + private DotGenerator() { + + } + + /** + * Generates the dot representation for the given graph. + * + * @param graph + * the graph + * @param colorSCCs + * specifies if the strongly connected components with size greater than shall be colored + * @param nameFunction + * use this function to provide custom names to nodes, null if the default toString shall be used + * @param colorFunction + * use this function to provide custom color to nodes, null if the default white color shall be used + * @param edgeFunction + * use this function to provide custom edge labels, null if no edge label shall be printed + * @return the dot representation as a string + */ + public static String generateDot(final Graph graph, final boolean colorSCCs, + final Function nameFunction, final Function colorFunction, + final Function> edgeFunction) { + final Map colorMap = new HashMap(); + + if (colorSCCs) { + final SCCResult result = SCC.computeSCC(graph); + final Set> sccs = result.getSccs(); + + int i = 0; + for (final Set scc : sccs) { + if (scc.size() > 1) { + for (final V node : scc) { + final String color = colorMap.get(node); + if (color == null) { + colorMap.put(node, colors[i % colors.length]); + } else { + colorMap.put(node, colorMap.get(node) + ":" + colors[i % colors.length]); + } + } + i++; + } + } + + // if a node has no color yet, then make it white + for (final V node : graph.getAllNodes()) { + if (!colorMap.containsKey(node)) { + colorMap.put(node, "white"); + } + } + } else { + for (final V node : graph.getAllNodes()) { + colorMap.put(node, "white"); + } + } + + if (colorFunction != null) { + for (final V node : graph.getAllNodes()) { + colorMap.put(node, colorFunction.apply(node)); + } + } + + final StringBuilder builder = new StringBuilder(); + builder.append("digraph g {\n"); + + for (final V node : graph.getAllNodes()) { + final String nodePresentation = nameFunction == null ? node.toString() : nameFunction.apply(node); + builder.append("\"" + nodePresentation + "\""); + builder.append("[style=filled,fillcolor=" + colorMap.get(node) + "]"); + builder.append(";\n"); + } + + for (final V source : graph.getAllNodes()) { + final IMemoryView targets = graph.getTargetNodes(source); + if (!targets.isEmpty()) { + final String sourcePresentation = nameFunction == null ? source.toString() : nameFunction.apply(source); + for (final V target : targets.distinctValues()) { + String edgeLabel = null; + if (edgeFunction != null) { + final Function v1 = edgeFunction.apply(source); + if (v1 != null) { + edgeLabel = v1.apply(target); + } + } + + final String targetPresentation = nameFunction == null ? target.toString() + : nameFunction.apply(target); + + builder.append("\"" + sourcePresentation + "\" -> \"" + targetPresentation + "\""); + if (edgeLabel != null) { + builder.append("[label=\"" + edgeLabel + "\"]"); + } + builder.append(";\n"); + } + } + } + + builder.append("}"); + return builder.toString(); + } + + /** + * Generates the dot representation for the given graph. No special pretty printing customization will be applied. + * + * @param graph + * the graph + * @return the dot representation as a string + */ + public static String generateDot(final Graph graph) { + return generateDot(graph, false, null, null, null); + } + + /** + * Returns a simple name shortener function that can be used in the graphviz visualization to help with readability. + * WARNING: if you shorten the name of the {@link Node}s too much, the visualization may become incorrect because + * grahpviz will treat different nodes as the same if their shortened names are the same. + * + * @param maxLength + * the maximum length of the text that is kept from the toString of the objects in the graph + * @return the shrunk toString value + */ + public static Function getNameShortener(final int maxLength) { + return new Function() { + @Override + public String apply(final V obj) { + final String value = obj.toString(); + return value.substring(0, Math.min(value.length(), maxLength)); + } + }; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/Graph.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/Graph.java new file mode 100644 index 00000000..1639fef4 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/graphimpl/Graph.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.graphimpl; + +import tools.refinery.interpreter.rete.itc.igraph.IGraphDataSource; +import tools.refinery.interpreter.rete.itc.igraph.IGraphObserver; +import tools.refinery.interpreter.rete.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class Graph implements IGraphDataSource, IBiDirectionalGraphDataSource { + + // source -> target -> count + private IMultiLookup outgoingEdges; + // target -> source -> count + private IMultiLookup incomingEdges; + + private Set nodes; + + private List> observers; + + public Graph() { + outgoingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + incomingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + nodes = CollectionsFactory.createSet(); + observers = CollectionsFactory.createObserverList(); + } + + public void insertEdge(V source, V target) { + outgoingEdges.addPair(source, target); + incomingEdges.addPair(target, source); + + for (IGraphObserver go : observers) { + go.edgeInserted(source, target); + } + } + + /** + * No-op if trying to delete edge that does not exist + * + * @since 2.0 + * @see #deleteEdgeIfExists(Object, Object) + */ + public void deleteEdgeIfExists(V source, V target) { + boolean containedEdge = outgoingEdges.lookupOrEmpty(source).containsNonZero(target); + if (containedEdge) { + deleteEdgeThatExists(source, target); + } + } + + /** + * @throws IllegalStateException + * if trying to delete edge that does not exist + * @since 2.0 + * @see #deleteEdgeIfExists(Object, Object) + */ + public void deleteEdgeThatExists(V source, V target) { + outgoingEdges.removePair(source, target); + incomingEdges.removePair(target, source); + for (IGraphObserver go : observers) { + go.edgeDeleted(source, target); + } + } + + /** + * @deprecated use explicitly {@link #deleteEdgeThatExists(Object, Object)} or + * {@link #deleteEdgeIfExists(Object, Object)} instead. To preserve backwards compatibility, this method + * delegates to the latter. + * + */ + @Deprecated + public void deleteEdge(V source, V target) { + deleteEdgeIfExists(source, target); + } + + /** + * Insert the given node into the graph. + */ + public void insertNode(V node) { + if (nodes.add(node)) { + for (IGraphObserver go : observers) { + go.nodeInserted(node); + } + } + } + + /** + * Deletes the given node AND all of the edges going in and out from the node. + */ + public void deleteNode(V node) { + if (nodes.remove(node)) { + IMemoryView incomingView = incomingEdges.lookup(node); + if (incomingView != null) { + Map incoming = CollectionsFactory.createMap(incomingView.asMap()); + + for (Entry entry : incoming.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + deleteEdgeThatExists(entry.getKey(), node); + } + } + } + + IMemoryView outgoingView = outgoingEdges.lookup(node); + if (outgoingView != null) { + Map outgoing = CollectionsFactory.createMap(outgoingView.asMap()); + + for (Entry entry : outgoing.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + deleteEdgeThatExists(node, entry.getKey()); + } + } + } + + for (IGraphObserver go : observers) { + go.nodeDeleted(node); + } + } + } + + @Override + public void attachObserver(IGraphObserver go) { + observers.add(go); + } + + @Override + public void attachAsFirstObserver(IGraphObserver observer) { + observers.add(0, observer); + } + + @Override + public void detachObserver(IGraphObserver go) { + observers.remove(go); + } + + @Override + public Set getAllNodes() { + return nodes; + } + + @Override + public IMemoryView getTargetNodes(V source) { + return outgoingEdges.lookupOrEmpty(source); + } + + @Override + public IMemoryView getSourceNodes(V target) { + return incomingEdges.lookupOrEmpty(target); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("nodes = "); + for (V n : getAllNodes()) { + sb.append(n.toString()); + sb.append(" "); + } + sb.append(" edges = "); + for (V source : outgoingEdges.distinctKeys()) { + IMemoryView targets = outgoingEdges.lookup(source); + for (V target : targets.distinctValues()) { + int count = targets.getCount(target); + for (int i = 0; i < count; i++) { + sb.append("(" + source + "," + target + ") "); + } + } + } + return sb.toString(); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalGraphDataSource.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalGraphDataSource.java new file mode 100644 index 00000000..8119e607 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalGraphDataSource.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiset; + +/** + * A bi-directional graph data source supports all operations that an {@link IGraphDataSource} does, but it + * also makes it possible to query the incoming edges of nodes, not only the outgoing edges. + * + * @author Tamas Szabo + * + * @param the type of the nodes in the graph + */ +public interface IBiDirectionalGraphDataSource extends IGraphDataSource { + + /** + * Returns the source nodes for the given target node. + * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source. + * + * The method must not return null. + * + * @param target the target node + * @return the multiset of source nodes + * @since 2.0 + */ + public IMemoryView getSourceNodes(V target); + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalWrapper.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalWrapper.java new file mode 100644 index 00000000..5e3f6430 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IBiDirectionalWrapper.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; + +/** + * This class can be used to wrap an {@link IGraphDataSource} into an {@link IBiDirectionalGraphDataSource}. This class + * provides support for the retrieval of source nodes for a given target which is not supported by standard + * {@link IGraphDataSource} implementations. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class IBiDirectionalWrapper implements IBiDirectionalGraphDataSource, IGraphObserver { + + private IGraphDataSource wrappedDataSource; + // target -> source -> count + private IMultiLookup incomingEdges; + + public IBiDirectionalWrapper(IGraphDataSource gds) { + this.wrappedDataSource = gds; + + this.incomingEdges = CollectionsFactory.createMultiLookup( + Object.class, MemoryType.MULTISETS, Object.class); + + if (gds.getAllNodes() != null) { + for (V source : gds.getAllNodes()) { + IMemoryView targets = gds.getTargetNodes(source); + for (V target : targets.distinctValues()) { + int count = targets.getCount(target); + for (int i = 0; i < count; i++) { + edgeInserted(source, target); + } + } + } + } + + gds.attachAsFirstObserver(this); + } + + @Override + public void attachObserver(IGraphObserver observer) { + wrappedDataSource.attachObserver(observer); + } + + @Override + public void attachAsFirstObserver(IGraphObserver observer) { + wrappedDataSource.attachAsFirstObserver(observer); + } + + @Override + public void detachObserver(IGraphObserver observer) { + wrappedDataSource.detachObserver(observer); + } + + @Override + public Set getAllNodes() { + return wrappedDataSource.getAllNodes(); + } + + @Override + public IMemoryView getTargetNodes(V source) { + return wrappedDataSource.getTargetNodes(source); + } + + @Override + public IMemoryView getSourceNodes(V target) { + return incomingEdges.lookupOrEmpty(target); + } + + @Override + public void edgeInserted(V source, V target) { + incomingEdges.addPair(target, source); + } + + @Override + public void edgeDeleted(V source, V target) { + incomingEdges.removePair(target, source); + } + + @Override + public void nodeInserted(V n) { + + } + + @Override + public void nodeDeleted(V node) { + + } + + @Override + public String toString() { + return wrappedDataSource.toString(); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphDataSource.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphDataSource.java new file mode 100644 index 00000000..615088ed --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphDataSource.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiset; + +import java.util.Set; + +/** + * The interface prescribes the set of operations that a graph data source must support. + *

Note that the old version of the interface is broken at version 1.6; + * MultiSets are now presented as Maps instead of Lists. + * + * @author Tamas Szabo + * + * @param + * the type of the nodes in the graph + */ +public interface IGraphDataSource { + + /** + * Attaches a new graph observer to this graph data source. Observers will be notified in the order they have been registered. + * + * @param observer the graph observer + */ + public void attachObserver(IGraphObserver observer); + + /** + * Attaches a new graph observer to this graph data source as the first one. + * In the notification order this observer will be the first one as long as another call to this method happens. + * + * @param observer the graph observer + * @since 1.6 + */ + public void attachAsFirstObserver(IGraphObserver observer); + + /** + * Detaches an already registered graph observer from this graph data source. + * + * @param observer the graph observer + */ + public void detachObserver(IGraphObserver observer); + + /** + * Returns the complete set of nodes in the graph data source. + * + * @return the set of all nodes + */ + public Set getAllNodes(); + + /** + * Returns the target nodes for the given source node. + * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source. + * + * The method must not return null. + * + * @param source the source node + * @return the multiset of target nodes + * @since 2.0 + */ + public IMemoryView getTargetNodes(V source); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphObserver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphObserver.java new file mode 100644 index 00000000..fb1ddcbd --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/IGraphObserver.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +/** + * Interface GraphObserver is used to observ the changes in a graph; edge and node insertion/deleteion. + * + * @author Tamas Szabo + * + */ +public interface IGraphObserver { + + /** + * Used to notify when an edge is inserted into the graph. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + */ + public void edgeInserted(V source, V target); + + /** + * Used to notify when an edge is deleted from the graph. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + */ + public void edgeDeleted(V source, V target); + + /** + * Used to notify when a node is inserted into the graph. + * + * @param n + * the node + */ + public void nodeInserted(V n); + + /** + * Used to notify when a node is deleted from the graph. + * + * @param n + * the node + */ + public void nodeDeleted(V n); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcDataSource.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcDataSource.java new file mode 100644 index 00000000..c9b086d8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcDataSource.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +import java.util.Set; + +import tools.refinery.interpreter.rete.itc.alg.misc.IGraphPathFinder; + +/** + * This interface defines those methods that a transitive reachability data source should provide. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the node + */ +public interface ITcDataSource { + + /** + * Attach a transitive closure relation observer. + * + * @param to + * the observer object + */ + public void attachObserver(ITcObserver to); + + /** + * Detach a transitive closure relation observer. + * + * @param to + * the observer object + */ + public void detachObserver(ITcObserver to); + + /** + * Returns all nodes which are reachable from the source node. + * + * @param source + * the source node + * @return the set of target nodes + */ + public Set getAllReachableTargets(V source); + + /** + * Returns all nodes from which the target node is reachable. + * + * @param target + * the target node + * @return the set of source nodes + */ + public Set getAllReachableSources(V target); + + /** + * Returns true if the target node is reachable from the source node. + * + * @param source + * the source node + * @param target + * the target node + * @return true if target is reachable from source, false otherwise + */ + public boolean isReachable(V source, V target); + + /** + * The returned {@link IGraphPathFinder} can be used to retrieve paths between nodes using transitive reachability. + * + * @return a path finder for the graph. + */ + public IGraphPathFinder getPathFinder(); + + /** + * Call this method to properly dispose the data structures of a transitive closure algorithm. + */ + public void dispose(); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcObserver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcObserver.java new file mode 100644 index 00000000..97f855ad --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/itc/igraph/ITcObserver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, 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.interpreter.rete.itc.igraph; + +/** + * Interface ITcObserver is used to observe the changes in a transitive closure relation; tuple insertion/deletion. + * + * @author Szabo Tamas + * + */ +public interface ITcObserver { + + /** + * Used to notify when a tuple is inserted into the transitive closure relation. + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + */ + public void tupleInserted(V source, V target); + + /** + * Used to notify when a tuple is deleted from the transitive closure relation. + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + */ + public void tupleDeleted(V source, V target); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/DRedReteBackendFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/DRedReteBackendFactory.java new file mode 100644 index 00000000..1e0352eb --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.matcher; + +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/HintConfigurator.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/HintConfigurator.java new file mode 100644 index 00000000..8e177303 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.matcher; + +import java.util.HashMap; +import java.util.Map; + +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/IncrementalMatcherCapability.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/IncrementalMatcherCapability.java new file mode 100644 index 00000000..735634de --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.matcher; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactory.java new file mode 100644 index 00000000..f84bd592 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.matcher; + +import tools.refinery.interpreter.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.interpreter.matchers.backend.IMatcherCapability; +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactoryProvider.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteBackendFactoryProvider.java new file mode 100644 index 00000000..91c50c80 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.matcher; + +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteEngine.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/ReteEngine.java new file mode 100644 index 00000000..aa793e11 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.boundary.Disconnectable; +import tools.refinery.interpreter.rete.boundary.ReteBoundary; +import tools.refinery.interpreter.rete.construction.RetePatternBuildException; +import tools.refinery.interpreter.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.NodeProvisioner; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.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 InterpreterRuntimeException + * 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 InterpreterRuntimeException + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/RetePatternMatcher.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/RetePatternMatcher.java new file mode 100644 index 00000000..5d04081c --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/RetePatternMatcher.java @@ -0,0 +1,453 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann 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.interpreter.rete.matcher; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; +import tools.refinery.interpreter.matchers.backend.IUpdateable; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Accuracy; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.IterableIndexer; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.rete.single.CallbackNode; +import tools.refinery.interpreter.rete.single.TransformerNode; +import tools.refinery.interpreter.rete.traceability.RecipeTraceInfo; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +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; + } + + /** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyConfiguration.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyConfiguration.java new file mode 100644 index 00000000..cd9e4266 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyReteBackendFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyReteBackendFactory.java new file mode 100644 index 00000000..8e9151ac --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/matcher/TimelyReteBackendFactory.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.interpreter.rete.matcher; + +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; + +/** + * 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(TimelyConfiguration.TimelineRepresentation.FIRST_ONLY, TimelyConfiguration.AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FIRST_ONLY_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelyConfiguration.TimelineRepresentation.FIRST_ONLY, TimelyConfiguration.AggregatorArchitecture.PARALLEL)); + public static final TimelyReteBackendFactory FAITHFUL_SEQUENTIAL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelyConfiguration.TimelineRepresentation.FAITHFUL, TimelyConfiguration.AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FAITHFUL_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelyConfiguration.TimelineRepresentation.FAITHFUL, TimelyConfiguration.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/Bag.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/Bag.java new file mode 100644 index 00000000..db3c7c7e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.misc; + +import java.util.Collection; +import java.util.LinkedList; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/ConstantNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/ConstantNode.java new file mode 100644 index 00000000..acb807ea --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.misc; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DefaultDeltaMonitor.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DefaultDeltaMonitor.java new file mode 100644 index 00000000..ff543936 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.misc; + +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DeltaMonitor.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/DeltaMonitor.java new file mode 100644 index 00000000..b32499d4 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.misc; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/SimpleReceiver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/misc/SimpleReceiver.java new file mode 100644 index 00000000..a6413490 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.misc; + +import java.util.Collection; +import java.util.Collections; + +import tools.refinery.interpreter.rete.network.BaseNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/BaseNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/BaseNode.java new file mode 100644 index 00000000..37022db6 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import tools.refinery.interpreter.rete.traceability.PatternTraceInfo; +import tools.refinery.interpreter.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(); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java new file mode 100644 index 00000000..c69757b6 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ConnectionFactory.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, 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.interpreter.rete.network; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.rete.aggregation.IndexerBasedAggregatorNode; +import tools.refinery.interpreter.rete.boundary.InputConnector; +import tools.refinery.interpreter.rete.eval.RelationEvaluatorNode; +import tools.refinery.interpreter.rete.index.DualInputNode; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.IterableIndexer; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.recipes.*; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.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(); + result.primary = (IterableIndexer) resolveIndexer((ProjectionIndexerRecipe) primarySlot.getRecipe()); + result.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/IGroupable.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/IGroupable.java new file mode 100644 index 00000000..cda84761 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import tools.refinery.interpreter.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); + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Network.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Network.java new file mode 100644 index 00000000..04fd645f --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Network.java @@ -0,0 +1,404 @@ +/******************************************************************************* + * 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.interpreter.rete.network; + +import tools.refinery.interpreter.rete.boundary.InputConnector; +import tools.refinery.interpreter.rete.recipes.ReteNodeRecipe; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.matcher.ReteEngine; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.rete.traceability.RecipeTraceInfo; +import tools.refinery.interpreter.rete.util.Options; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * @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 final Map globalTerminationCriteria; + 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)); + globalTerminationCriteria = null; + } + 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) { + Thread.currentThread().interrupt(); + } + } + } + } 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) { + Thread.currentThread().interrupt(); + } + } + 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NetworkStructureChangeSensitiveNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NetworkStructureChangeSensitiveNode.java new file mode 100644 index 00000000..51341424 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Node.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Node.java new file mode 100644 index 00000000..e7faf374 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import java.util.Set; + +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java new file mode 100644 index 00000000..301b757d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeFactory.java @@ -0,0 +1,376 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, 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.interpreter.rete.network; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.EMap; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; +import tools.refinery.interpreter.matchers.psystem.IRelationEvaluator; +import tools.refinery.interpreter.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.rete.aggregation.ColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.CountNode; +import tools.refinery.interpreter.rete.aggregation.IAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode; +import tools.refinery.interpreter.rete.aggregation.timely.FirstOnlySequentialTimelyColumnAggregatorNode; +import tools.refinery.interpreter.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.interpreter.rete.boundary.ExternalInputStatelessFilterNode; +import tools.refinery.interpreter.rete.eval.EvaluatorCore; +import tools.refinery.interpreter.rete.eval.MemorylessEvaluatorNode; +import tools.refinery.interpreter.rete.eval.OutputCachingEvaluatorNode; +import tools.refinery.interpreter.rete.eval.RelationEvaluatorNode; +import tools.refinery.interpreter.rete.index.ExistenceNode; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.JoinNode; +import tools.refinery.interpreter.rete.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.interpreter.rete.itc.alg.representative.StronglyConnectedComponentAlgorithm; +import tools.refinery.interpreter.rete.itc.alg.representative.WeaklyConnectedComponentAlgorithm; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration.AggregatorArchitecture; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.interpreter.rete.misc.ConstantNode; +import tools.refinery.interpreter.rete.recipes.*; +import tools.refinery.interpreter.rete.single.*; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java new file mode 100644 index 00000000..0a68f449 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.boundary.InputConnector; +import tools.refinery.interpreter.rete.construction.plancompiler.CompilerHelper; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.OnetimeIndexer; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.interpreter.rete.recipes.*; +import tools.refinery.interpreter.rete.recipes.helper.RecipeRecognizer; +import tools.refinery.interpreter.rete.recipes.helper.RecipesHelper; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.rete.remote.RemoteReceiver; +import tools.refinery.interpreter.rete.remote.RemoteSupplier; +import tools.refinery.interpreter.rete.traceability.ActiveNodeConflictTrace; +import tools.refinery.interpreter.rete.traceability.RecipeTraceInfo; +import tools.refinery.interpreter.rete.traceability.UserRequestTrace; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/PosetAwareReceiver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/PosetAwareReceiver.java new file mode 100644 index 00000000..7904eca5 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ProductionNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ProductionNode.java new file mode 100644 index 00000000..2a167078 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import java.util.Map; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Receiver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Receiver.java new file mode 100644 index 00000000..e033ebe5 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/RederivableNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/RederivableNode.java new file mode 100644 index 00000000..6b7ba504 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReinitializedNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReinitializedNode.java new file mode 100644 index 00000000..959d09fe --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReinitializedNode.java @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.rete.network; + +import tools.refinery.interpreter.matchers.tuple.Tuple; + +import java.util.Collection; + +public interface ReinitializedNode { + void reinitializeWith(Collection tuples); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReteContainer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReteContainer.java new file mode 100644 index 00000000..b5c91d32 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/ReteContainer.java @@ -0,0 +1,732 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann 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.interpreter.rete.network; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.rete.boundary.InputConnector; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.timely.TimelyCommunicationTracker; +import tools.refinery.interpreter.rete.network.delayed.DelayedCommand; +import tools.refinery.interpreter.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.interpreter.rete.network.delayed.DelayedDisconnectCommand; +import tools.refinery.interpreter.CancellationToken; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.remote.Address; +import tools.refinery.interpreter.rete.single.SingleInputNode; +import tools.refinery.interpreter.rete.single.TrimmerNode; +import tools.refinery.interpreter.rete.util.Options; + +import java.util.*; +import java.util.function.Function; + +/** + * @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; + + private final CancellationToken cancellationToken; + + /** + * @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(); + cancellationToken = backendContext.getRuntimeContext().getCancellationToken(); + + 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) { + Thread.currentThread().interrupt(); + 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!"); + } + + if (group != null) { + 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(); + } + + public void checkCancelled() { + cancellationToken.checkCancelled(); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/StandardNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/StandardNode.java new file mode 100644 index 00000000..5a9a2644 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/StandardNode.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann 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.interpreter.rete.network; + +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.index.GenericProjectionIndexer; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 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) { + reteContainer.checkCancelled(); + 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Supplier.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Supplier.java new file mode 100644 index 00000000..83d05646 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.single.TrimmerNode; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Tunnel.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/Tunnel.java new file mode 100644 index 00000000..9dcb4b6e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/UpdateMessage.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/UpdateMessage.java new file mode 100644 index 00000000..bd64cc9d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationGroup.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationGroup.java new file mode 100644 index 00000000..5d9ee3b4 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationTracker.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationTracker.java new file mode 100644 index 00000000..2e8eb338 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/CommunicationTracker.java @@ -0,0 +1,454 @@ +/******************************************************************************* + * 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.interpreter.rete.network.communication; + +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.rete.aggregation.IAggregatorNode; +import tools.refinery.interpreter.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.interpreter.rete.eval.RelationEvaluatorNode; +import tools.refinery.interpreter.rete.index.*; +import tools.refinery.interpreter.rete.itc.alg.incscc.IncSCCAlg; +import tools.refinery.interpreter.rete.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.network.*; +import tools.refinery.interpreter.rete.network.communication.timely.TimelyIndexerListenerProxy; +import tools.refinery.interpreter.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.interpreter.rete.network.mailbox.FallThroughCapableMailbox; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.single.TransitiveClosureNode; +import tools.refinery.interpreter.rete.single.TrimmerNode; + +import java.util.*; + +/** + * 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(); + if (group == null) { + throw new IllegalStateException("Group queue must not be empty"); + } + 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/MessageSelector.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/MessageSelector.java new file mode 100644 index 00000000..2e8978b7 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/NodeComparator.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/NodeComparator.java new file mode 100644 index 00000000..498191d8 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication; + +import java.util.Comparator; +import java.util.Map; + +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/PhasedSelector.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/PhasedSelector.java new file mode 100644 index 00000000..1739494a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/Timestamp.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/Timestamp.java new file mode 100644 index 00000000..dbf4c6ee --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/RecursiveCommunicationGroup.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/RecursiveCommunicationGroup.java new file mode 100644 index 00000000..3c0feb7c --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.RederivableNode; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.PhasedSelector; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/SingletonCommunicationGroup.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/SingletonCommunicationGroup.java new file mode 100644 index 00000000..5b50f375 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.PhasedSelector; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/TimelessCommunicationTracker.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timeless/TimelessCommunicationTracker.java new file mode 100644 index 00000000..77f40fc3 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.index.DualInputNode; +import tools.refinery.interpreter.rete.index.Indexer; +import tools.refinery.interpreter.rete.index.IndexerListener; +import tools.refinery.interpreter.rete.index.IterableIndexer; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.RederivableNode; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/ResumableNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/ResumableNode.java new file mode 100644 index 00000000..85b9809d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timely; + +import tools.refinery.interpreter.rete.network.IGroupable; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationGroup.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationGroup.java new file mode 100644 index 00000000..e9523015 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationTracker.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyCommunicationTracker.java new file mode 100644 index 00000000..a0cdc701 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.rete.index.IndexerListener; +import tools.refinery.interpreter.rete.index.SpecializedProjectionIndexer; +import tools.refinery.interpreter.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.interpreter.rete.index.StandardIndexer; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.NodeComparator; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyIndexerListenerProxy.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyIndexerListenerProxy.java new file mode 100644 index 00000000..07916270 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timely; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.rete.index.IndexerListener; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyMailboxProxy.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimelyMailboxProxy.java new file mode 100644 index 00000000..56cec326 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timely; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimestampTransformation.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/communication/timely/TimestampTransformation.java new file mode 100644 index 00000000..e995f0cb --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.communication.timely; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedCommand.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedCommand.java new file mode 100644 index 00000000..eaf35f49 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.delayed; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.network.Network; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedConnectCommand.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedConnectCommand.java new file mode 100644 index 00000000..00077f39 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.delayed; + +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedDisconnectCommand.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/delayed/DelayedDisconnectCommand.java new file mode 100644 index 00000000..4b161388 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.delayed; + +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/DefaultMessageIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/DefaultMessageIndexer.java new file mode 100644 index 00000000..bbc01f1a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/GroupBasedMessageIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/GroupBasedMessageIndexer.java new file mode 100644 index 00000000..1bbc863f --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/MessageIndexer.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/indexer/MessageIndexer.java new file mode 100644 index 00000000..c7ef28a1 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.indexer; + +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Clearable; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/AdaptableMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/AdaptableMailbox.java new file mode 100644 index 00000000..4abbe090 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox; + +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/FallThroughCapableMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/FallThroughCapableMailbox.java new file mode 100644 index 00000000..14c6bc03 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox; + +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.Receiver; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/Mailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/Mailbox.java new file mode 100644 index 00000000..92da4735 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.IGroupable; +import tools.refinery.interpreter.rete.network.Receiver; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/MessageIndexerFactory.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/MessageIndexerFactory.java new file mode 100644 index 00000000..faf517c3 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox; + +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java new file mode 100644 index 00000000..31ee2842 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox.timeless; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.indexer.MessageIndexer; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/BehaviorChangingMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/BehaviorChangingMailbox.java new file mode 100644 index 00000000..86a3f125 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox.timeless; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.interpreter.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; + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/DefaultMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/DefaultMailbox.java new file mode 100644 index 00000000..5bb8386d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/DefaultMailbox.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * 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.interpreter.rete.network.mailbox.timeless; + +import java.util.Map; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.PhasedSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/PosetAwareMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/PosetAwareMailbox.java new file mode 100644 index 00000000..f83a77cf --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox.timeless; + +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.PhasedSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.PosetAwareReceiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/UpdateSplittingMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timeless/UpdateSplittingMailbox.java new file mode 100644 index 00000000..8e85291f --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox.timeless; + +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.PhasedSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.indexer.DefaultMessageIndexer; +import tools.refinery.interpreter.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timely/TimelyMailbox.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/mailbox/timely/TimelyMailbox.java new file mode 100644 index 00000000..09f8fb58 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.network.mailbox.timely; + +import java.util.Map; +import java.util.TreeMap; + +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.MessageSelector; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/Address.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/Address.java new file mode 100644 index 00000000..bed5bd6a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.remote; + +import tools.refinery.interpreter.rete.network.Node; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteReceiver.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteReceiver.java new file mode 100644 index 00000000..46bd7551 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.remote; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.single.SingleInputNode; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteSupplier.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/remote/RemoteSupplier.java new file mode 100644 index 00000000..759b1db5 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.remote; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.single.SingleInputNode; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/AbstractUniquenessEnforcerNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/AbstractUniquenessEnforcerNode.java new file mode 100644 index 00000000..c55a294a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.Tunnel; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.interpreter.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); + } + } + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/CallbackNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/CallbackNode.java new file mode 100644 index 00000000..f4e3ba0d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.backend.IUpdateable; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.rete.misc.SimpleReceiver; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DefaultProductionNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DefaultProductionNode.java new file mode 100644 index 00000000..e929b688 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.rete.traceability.CompiledQuery; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorBucketNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorBucketNode.java new file mode 100644 index 00000000..b08a7330 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorDispatcherNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/DiscriminatorDispatcherNode.java new file mode 100644 index 00000000..65c73787 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.Receiver; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/EqualityFilterNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/EqualityFilterNode.java new file mode 100644 index 00000000..67d12743 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/FilterNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/FilterNode.java new file mode 100644 index 00000000..341df0f2 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/InequalityFilterNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/InequalityFilterNode.java new file mode 100644 index 00000000..4b7b5b78 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/RepresentativeElectionNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/RepresentativeElectionNode.java new file mode 100644 index 00000000..75aa32ef --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReinitializedNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.interpreter.rete.itc.alg.representative.RepresentativeObserver; +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/SingleInputNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/SingleInputNode.java new file mode 100644 index 00000000..3d44ddaa --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.StandardNode; +import tools.refinery.interpreter.rete.network.Supplier; +import tools.refinery.interpreter.rete.network.Tunnel; +import tools.refinery.interpreter.rete.network.communication.CommunicationTracker; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * @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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyProductionNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyProductionNode.java new file mode 100644 index 00000000..2c71da25 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyProductionNode.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.interpreter.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ProductionNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.traceability.TraceInfo; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.rete.traceability.CompiledQuery; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyUniquenessEnforcerNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TimelyUniquenessEnforcerNode.java new file mode 100644 index 00000000..7a280d8e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.interpreter.rete.matcher.TimelyConfiguration; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timely.ResumableNode; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; +import tools.refinery.interpreter.rete.index.timely.TimelyMemoryIdentityIndexer; +import tools.refinery.interpreter.rete.index.timely.TimelyMemoryNullIndexer; + +/** + * 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() == TimelyConfiguration.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(); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransformerNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransformerNode.java new file mode 100644 index 00000000..0cb17f7b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransitiveClosureNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransitiveClosureNode.java new file mode 100644 index 00000000..3034933d --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.interpreter.rete.network.ReinitializedNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.itc.alg.incscc.IncSCCAlg; +import tools.refinery.interpreter.rete.itc.alg.misc.Tuple; +import tools.refinery.interpreter.rete.itc.graphimpl.Graph; +import tools.refinery.interpreter.rete.itc.igraph.ITcDataSource; +import tools.refinery.interpreter.rete.itc.igraph.ITcObserver; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +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.interpreter.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.interpreter.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.interpreter.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO); + } + + @Override + public void tupleDeleted(Object source, Object target) { + tools.refinery.interpreter.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransparentNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TransparentNode.java new file mode 100644 index 00000000..fb64ed88 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TrimmerNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/TrimmerNode.java new file mode 100644 index 00000000..96e757e3 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/UniquenessEnforcerNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/UniquenessEnforcerNode.java new file mode 100644 index 00000000..195e3537 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.rete.network.PosetAwareReceiver; +import tools.refinery.interpreter.rete.network.RederivableNode; +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.rete.network.communication.CommunicationGroup; +import tools.refinery.interpreter.rete.network.communication.Timestamp; +import tools.refinery.interpreter.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.interpreter.rete.network.mailbox.Mailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.interpreter.rete.network.mailbox.timeless.PosetAwareMailbox; +import tools.refinery.interpreter.matchers.context.IPosetComparator; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.IMultiset; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.rete.index.MemoryIdentityIndexer; +import tools.refinery.interpreter.rete.index.MemoryNullIndexer; +import tools.refinery.interpreter.rete.index.ProjectionIndexer; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/ValueBinderFilterNode.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/single/ValueBinderFilterNode.java new file mode 100644 index 00000000..6a56984c --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.single; + +import tools.refinery.interpreter.rete.network.ReteContainer; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ActiveNodeConflictTrace.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ActiveNodeConflictTrace.java new file mode 100644 index 00000000..30eac47b --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.traceability; + +import tools.refinery.interpreter.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; + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledQuery.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledQuery.java new file mode 100644 index 00000000..38665685 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.traceability; + +import java.util.Map; + +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledSubPlan.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/CompiledSubPlan.java new file mode 100644 index 00000000..320b9d19 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ParameterProjectionTrace.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/ParameterProjectionTrace.java new file mode 100644 index 00000000..d743608a --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; + +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PatternTraceInfo.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PatternTraceInfo.java new file mode 100644 index 00000000..37a3b9ef --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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(); +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PlanningTrace.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/PlanningTrace.java new file mode 100644 index 00000000..78f3941e --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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()); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/RecipeTraceInfo.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/RecipeTraceInfo.java new file mode 100644 index 00000000..1f9d3d91 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.rete.network.Node; +import tools.refinery.interpreter.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; + } + + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/TraceInfo.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/TraceInfo.java new file mode 100644 index 00000000..f7ce1933 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.traceability; + +import tools.refinery.interpreter.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(); diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/UserRequestTrace.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/traceability/UserRequestTrace.java new file mode 100644 index 00000000..2c163430 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.traceability; + +import java.util.Collection; + +import tools.refinery.interpreter.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); + } +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/LexicographicComparator.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/LexicographicComparator.java new file mode 100644 index 00000000..daf3bf63 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/Options.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/Options.java new file mode 100644 index 00000000..e372f015 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.util; + +import tools.refinery.interpreter.rete.construction.basiclinear.BasicLinearLayout; +import tools.refinery.interpreter.rete.construction.quasitree.QuasiTreeLayout; +import tools.refinery.interpreter.rete.network.communication.timely.TimelyCommunicationGroup; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.planning.IQueryPlannerStrategy; + +/** + * 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/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/OrderingCompareAgent.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/OrderingCompareAgent.java new file mode 100644 index 00000000..20fd35dd --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/OrderingCompareAgent.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.interpreter.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 + */ + protected 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 reverse(c1.compareTo(c2)); + } + + protected static int preferMore(U c1, U c2, Comparator comp) { + return reverse(comp.compare(c1, c2)); + } + + private static int reverse(int value) { + return Integer.compare(0, value); + } + +} diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/ReteHintOptions.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/util/ReteHintOptions.java new file mode 100644 index 00000000..42fcd406 --- /dev/null +++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/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.interpreter.rete.util; + +import tools.refinery.interpreter.rete.matcher.DRedReteBackendFactory; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.backend.QueryHintOption; + +/** + * 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