aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java')
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java517
1 files changed, 517 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java
new file mode 100644
index 00000000..90fcad4d
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java
@@ -0,0 +1,517 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.matchers.util;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.NavigableMap;
15import java.util.Set;
16import java.util.TreeMap;
17
18import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
19import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
20import tools.refinery.viatra.runtime.matchers.util.resumable.Resumable;
21import tools.refinery.viatra.runtime.matchers.util.resumable.UnmaskedResumable;
22import tools.refinery.viatra.runtime.matchers.util.timeline.Diff;
23import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline;
24import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines;
25
26/**
27 * A timely memory implementation that incrementally maintains the {@link Timeline}s of tuples. The memory is capable of
28 * lazy folding (see {@link Resumable}).
29 *
30 * @author Tamas Szabo
31 * @since 2.3
32 */
33public class TimelyMemory<Timestamp extends Comparable<Timestamp>> implements Clearable, UnmaskedResumable<Timestamp> {
34
35 protected final Map<Tuple, TreeMap<Timestamp, CumulativeCounter>> counters;
36 protected final Map<Tuple, Timeline<Timestamp>> timelines;
37 public final TreeMap<Timestamp, Map<Tuple, FoldingState>> foldingState;
38 protected final Set<Tuple> presentAtInfinity;
39 protected final boolean isLazy;
40 protected final Diff<Timestamp> EMPTY_DIFF = new Diff<Timestamp>();
41
42 public TimelyMemory() {
43 this(false);
44 }
45
46 public TimelyMemory(final boolean isLazy) {
47 this.counters = CollectionsFactory.createMap();
48 this.timelines = CollectionsFactory.createMap();
49 this.presentAtInfinity = CollectionsFactory.createSet();
50 this.isLazy = isLazy;
51 if (isLazy) {
52 this.foldingState = CollectionsFactory.createTreeMap();
53 } else {
54 this.foldingState = null;
55 }
56 }
57
58 @Override
59 public Set<Tuple> getResumableTuples() {
60 if (this.foldingState == null || this.foldingState.isEmpty()) {
61 return Collections.emptySet();
62 } else {
63 return this.foldingState.firstEntry().getValue().keySet();
64 }
65 }
66
67 @Override
68 public Timestamp getResumableTimestamp() {
69 if (this.foldingState == null || this.foldingState.isEmpty()) {
70 return null;
71 } else {
72 return this.foldingState.firstKey();
73 }
74 }
75
76 /**
77 * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the
78 * two states will be merged together.
79 */
80 protected void addFoldingState(final Tuple tuple, final FoldingState state, final Timestamp timestamp) {
81 assert state.diff != 0;
82 final Map<Tuple, FoldingState> tupleMap = this.foldingState.computeIfAbsent(timestamp,
83 k -> CollectionsFactory.createMap());
84 tupleMap.compute(tuple, (k, v) -> {
85 return v == null ? state : v.merge(state);
86 });
87 }
88
89 @Override
90 public Map<Tuple, Diff<Timestamp>> resumeAt(final Timestamp timestamp) {
91 Timestamp current = this.getResumableTimestamp();
92 if (current == null) {
93 throw new IllegalStateException("There is othing to fold!");
94 } else if (current.compareTo(timestamp) != 0) {
95 // It can happen that already registered folding states end up having zero diffs,
96 // and we are instructed to continue folding at a timestamp that is higher
97 // than the lowest timestamp with a folding state.
98 // However, we only do garbage collection in doFoldingState, so now it is time to
99 // first clean up those states with zero diffs.
100 while (current != null && current.compareTo(timestamp) < 0) {
101 final Map<Tuple, FoldingState> tupleMap = this.foldingState.remove(current);
102 for (final Entry<Tuple, FoldingState> entry : tupleMap.entrySet()) {
103 final Tuple key = entry.getKey();
104 final FoldingState value = entry.getValue();
105 if (value.diff != 0) {
106 throw new IllegalStateException("Expected zero diff during garbage collection at " + current
107 + ", but the diff was " + value.diff + "!");
108 }
109 doFoldingStep(key, value, current);
110 }
111 current = this.getResumableTimestamp();
112 }
113 if (current == null || current.compareTo(timestamp) != 0) {
114 throw new IllegalStateException("Expected to continue folding at " + timestamp + "!");
115 }
116 }
117
118 final Map<Tuple, Diff<Timestamp>> diffMap = CollectionsFactory.createMap();
119 final Map<Tuple, FoldingState> tupleMap = this.foldingState.remove(timestamp);
120 for (final Entry<Tuple, FoldingState> entry : tupleMap.entrySet()) {
121 final Tuple key = entry.getKey();
122 final FoldingState value = entry.getValue();
123 diffMap.put(key, doFoldingStep(key, value, timestamp));
124 }
125
126 if (this.foldingState.get(timestamp) != null) {
127 throw new IllegalStateException(
128 "Folding at " + timestamp + " produced more folding work at the same timestamp!");
129 }
130
131 return diffMap;
132 }
133
134 protected Diff<Timestamp> doFoldingStep(final Tuple tuple, final FoldingState state, final Timestamp timestamp) {
135 final CumulativeCounter counter = getCounter(tuple, timestamp);
136 if (state.diff == 0) {
137 gcCounters(counter, tuple, timestamp);
138 return EMPTY_DIFF;
139 } else {
140 final Diff<Timestamp> resultDiff = new Diff<>();
141 final Timestamp nextTimestamp = this.counters.get(tuple).higherKey(timestamp);
142
143 final int oldCumulative = counter.cumulative;
144
145 counter.cumulative += state.diff;
146
147 computeDiffsLazy(state.diff < 0 ? Direction.DELETE : Direction.INSERT, oldCumulative, counter.cumulative,
148 timestamp, nextTimestamp, resultDiff);
149
150 gcCounters(counter, tuple, timestamp);
151 updateTimeline(tuple, resultDiff);
152
153 // prepare folding state for next timestamp
154 if (nextTimestamp != null) {
155 // propagate the incoming diff, not the diff stored in counter
156 addFoldingState(tuple, new FoldingState(state.diff), nextTimestamp);
157 }
158
159 return resultDiff;
160 }
161 }
162
163 /**
164 * On-demand initializes and returns the counter for the given tuple and timestamp.
165 */
166 protected CumulativeCounter getCounter(final Tuple tuple, final Timestamp timestamp) {
167 final TreeMap<Timestamp, CumulativeCounter> counterTimeline = this.counters.computeIfAbsent(tuple,
168 k -> CollectionsFactory.createTreeMap());
169
170 final CumulativeCounter counter = counterTimeline.computeIfAbsent(timestamp, k -> {
171 final Entry<Timestamp, CumulativeCounter> previousCounter = counterTimeline.lowerEntry(k);
172 final int previousCumulative = previousCounter == null ? 0 : previousCounter.getValue().cumulative;
173 return new CumulativeCounter(0, previousCumulative);
174 });
175
176 return counter;
177 }
178
179 /**
180 * Garbage collects the counter of the given tuple and timestamp if the new diff is zero.
181 */
182 protected void gcCounters(final CumulativeCounter counter, final Tuple tuple, final Timestamp timestamp) {
183 if (counter.diff == 0) {
184 final TreeMap<Timestamp, CumulativeCounter> counterMap = this.counters.get(tuple);
185 counterMap.remove(timestamp);
186 if (counterMap.isEmpty()) {
187 this.counters.remove(tuple);
188 }
189 }
190 }
191
192 /**
193 * Utility method that computes the timeline diffs in case of lazy memories. The diffs will be inserted into the
194 * input parameter. This method computes diffs for entire plateaus that spans from timestamp to nextTimestamp.
195 *
196 * Compared to the eager version of this method, the lazy version makes use of both the old and the new cumulative
197 * values because it can happen that the cumulative is incremented by a value that is larger than 1 (as folding
198 * states are merged together). This means that we cant decide whether the cumulative became positive by comparing
199 * the new value to 1.
200 */
201 protected void computeDiffsLazy(final Direction direction, final int oldCumulative, final int newCumulative,
202 final Timestamp timestamp, final Timestamp nextTimestamp, final Diff<Timestamp> diffs) {
203 if (direction == Direction.INSERT) {
204 if (newCumulative == 0) {
205 throw new IllegalStateException("Cumulative count can never be negative!");
206 } else {
207 if (oldCumulative == 0 /* current became positive */) {
208 // (1) either we sent out a DELETE before and now we need to cancel it,
209 // (2) or we just INSERT this for the first time
210 diffs.add(new Signed<>(Direction.INSERT, timestamp));
211 if (nextTimestamp != null) {
212 diffs.add(new Signed<>(Direction.DELETE, nextTimestamp));
213 }
214 } else /* current stays positive */ {
215 // nothing to do
216 }
217 }
218 } else {
219 if (newCumulative < 0) {
220 throw new IllegalStateException("Cumulative count can never be negative!");
221 } else {
222 if (newCumulative == 0 /* current became zero */) {
223 diffs.add(new Signed<>(Direction.DELETE, timestamp));
224 if (nextTimestamp != null) {
225 diffs.add(new Signed<>(Direction.INSERT, nextTimestamp));
226 }
227 } else /* current stays positive */ {
228 // nothing to do
229 }
230 }
231 }
232 }
233
234 /**
235 * Utility method that computes the timeline diffs in case of eager memories. The diffs will be inserted into the
236 * input parameter. This method computes diffs that describe momentary changes instead of plateaus. Returns a
237 * {@link SignChange} that describes how the sign has changed at the given timestamp.
238 */
239 protected SignChange computeDiffsEager(final Direction direction, final CumulativeCounter counter,
240 final SignChange signChangeAtPrevious, final Timestamp timestamp, final Diff<Timestamp> diffs) {
241 if (direction == Direction.INSERT) {
242 if (counter.cumulative == 0) {
243 throw new IllegalStateException("Cumulative count can never be negative!");
244 } else {
245 if (counter.cumulative == 1 /* current became positive */) {
246 if (signChangeAtPrevious != SignChange.BECAME_POSITIVE) {
247 // (1) either we sent out a DELETE before and now we need to cancel it,
248 // (2) or we just INSERT this for the first time
249 diffs.add(new Signed<>(Direction.INSERT, timestamp));
250 } else {
251 // we have already emitted this at the previous timestamp
252 // both previous and current became positive
253 throw new IllegalStateException(
254 "This would mean that the diff at current is 0 " + counter.diff);
255 }
256
257 // remember for next timestamp
258 return SignChange.BECAME_POSITIVE;
259 } else /* current stays positive */ {
260 if (signChangeAtPrevious == SignChange.BECAME_POSITIVE) {
261 // we sent out an INSERT before and now the timeline is positive already starting at previous
262 // we need to cancel the effect of this with a DELETE
263 diffs.add(new Signed<>(Direction.DELETE, timestamp));
264 } else {
265 // this is normal, both previous and current was positive and stays positive
266 }
267
268 // remember for next timestamp
269 return SignChange.IRRELEVANT;
270 }
271 }
272 } else {
273 if (counter.cumulative < 0) {
274 throw new IllegalStateException("Cumulative count can never be negative!");
275 } else {
276 if (counter.cumulative == 0 /* current became zero */) {
277 if (signChangeAtPrevious != SignChange.BECAME_ZERO) {
278 // (1) either we sent out a INSERT before and now we need to cancel it,
279 // (2) or we just DELETE this for the first time
280 diffs.add(new Signed<>(Direction.DELETE, timestamp));
281 } else {
282 // we have already emitted this at the previous timestamp
283 // both previous and current became zero
284 throw new IllegalStateException(
285 "This would mean that the diff at current is 0 " + counter.diff);
286 }
287
288 // remember for next timestamp
289 return SignChange.BECAME_ZERO;
290 } else /* current stays positive */ {
291 if (signChangeAtPrevious == SignChange.BECAME_ZERO) {
292 // we sent out a DELETE before and now the timeline is zero already starting at previous
293 // we need to cancel the effect of this with a INSERT
294 diffs.add(new Signed<>(Direction.INSERT, timestamp));
295 } else {
296 // this is normal, both previous and current was positive and stays positive
297 }
298
299 // remember for next timestamp
300 return SignChange.IRRELEVANT;
301 }
302 }
303 }
304 }
305
306 public Diff<Timestamp> put(final Tuple tuple, final Timestamp timestamp) {
307 if (this.isLazy) {
308 return putLazy(tuple, timestamp);
309 } else {
310 return putEager(tuple, timestamp);
311 }
312 }
313
314 public Diff<Timestamp> remove(final Tuple tuple, final Timestamp timestamp) {
315 if (this.isLazy) {
316 return removeLazy(tuple, timestamp);
317 } else {
318 return removeEager(tuple, timestamp);
319 }
320 }
321
322 protected Diff<Timestamp> putEager(final Tuple tuple, final Timestamp timestamp) {
323 final Diff<Timestamp> resultDiff = new Diff<>();
324 final CumulativeCounter counter = getCounter(tuple, timestamp);
325 ++counter.diff;
326
327 // before the INSERT timestamp, no change at all
328 // it cannot happen that those became positive in this round
329 SignChange signChangeAtPrevious = SignChange.IRRELEVANT;
330
331 final NavigableMap<Timestamp, CumulativeCounter> nextCounters = this.counters.get(tuple).tailMap(timestamp,
332 true);
333 for (final Entry<Timestamp, CumulativeCounter> currentEntry : nextCounters.entrySet()) {
334 final Timestamp currentTimestamp = currentEntry.getKey();
335 final CumulativeCounter currentCounter = currentEntry.getValue();
336 ++currentCounter.cumulative;
337 signChangeAtPrevious = computeDiffsEager(Direction.INSERT, currentCounter, signChangeAtPrevious,
338 currentTimestamp, resultDiff);
339 }
340
341 gcCounters(counter, tuple, timestamp);
342 updateTimeline(tuple, resultDiff);
343
344 return resultDiff;
345 }
346
347 protected Diff<Timestamp> putLazy(final Tuple tuple, final Timestamp timestamp) {
348 final CumulativeCounter counter = getCounter(tuple, timestamp);
349 counter.diff += 1;
350 // before the INSERT timestamp, no change at all
351 // it cannot happen that those became positive in this round
352 addFoldingState(tuple, new FoldingState(+1), timestamp);
353 return EMPTY_DIFF;
354 }
355
356 protected Diff<Timestamp> removeEager(final Tuple tuple, final Timestamp timestamp) {
357 final Diff<Timestamp> resultDiff = new Diff<>();
358 final CumulativeCounter counter = getCounter(tuple, timestamp);
359 --counter.diff;
360
361 // before the DELETE timestamp, no change at all
362 // it cannot happen that those became zero in this round
363 SignChange signChangeAtPrevious = SignChange.IRRELEVANT;
364
365 final NavigableMap<Timestamp, CumulativeCounter> nextCounters = this.counters.get(tuple).tailMap(timestamp,
366 true);
367 for (final Entry<Timestamp, CumulativeCounter> currentEntry : nextCounters.entrySet()) {
368 final Timestamp currentTimestamp = currentEntry.getKey();
369 final CumulativeCounter currentCounter = currentEntry.getValue();
370 --currentCounter.cumulative;
371 signChangeAtPrevious = computeDiffsEager(Direction.DELETE, currentCounter, signChangeAtPrevious,
372 currentTimestamp, resultDiff);
373 }
374
375 gcCounters(counter, tuple, timestamp);
376 updateTimeline(tuple, resultDiff);
377
378 return resultDiff;
379 }
380
381 protected Diff<Timestamp> removeLazy(final Tuple tuple, final Timestamp timestamp) {
382 final CumulativeCounter counter = getCounter(tuple, timestamp);
383 counter.diff -= 1;
384 // before the DELETE timestamp, no change at all
385 // it cannot happen that those became zero in this round
386 addFoldingState(tuple, new FoldingState(-1), timestamp);
387 return EMPTY_DIFF;
388 }
389
390 /**
391 * Updates and garbage collects the timeline of the given tuple based on the given timeline diff.
392 */
393 protected void updateTimeline(final Tuple tuple, final Diff<Timestamp> diff) {
394 if (!diff.isEmpty()) {
395 this.timelines.compute(tuple, (k, oldTimeline) -> {
396 this.presentAtInfinity.remove(tuple);
397 final Timeline<Timestamp> timeline = oldTimeline == null ? Timelines.createFrom(diff)
398 : oldTimeline.mergeAdditive(diff);
399 if (timeline.isPresentAtInfinity()) {
400 this.presentAtInfinity.add(tuple);
401 }
402 if (timeline.isEmpty()) {
403 return null;
404 } else {
405 return timeline;
406 }
407 });
408 }
409 }
410
411 /**
412 * @since 2.8
413 */
414 public Set<Tuple> getTuplesAtInfinity() {
415 return this.presentAtInfinity;
416 }
417
418 /**
419 * Returns the number of tuples that are present at the moment 'infinity'.
420 */
421 public int getCountAtInfinity() {
422 return this.presentAtInfinity.size();
423 }
424
425 /**
426 * Returns true if the given tuple is present at the moment 'infinity'.
427 */
428 public boolean isPresentAtInfinity(final Tuple tuple) {
429 final Timeline<Timestamp> timeline = this.timelines.get(tuple);
430 if (timeline == null) {
431 return false;
432 } else {
433 return timeline.isPresentAtInfinity();
434 }
435 }
436
437 public boolean isEmpty() {
438 return this.counters.isEmpty();
439 }
440
441 public int size() {
442 return this.counters.size();
443 }
444
445 public Set<Tuple> keySet() {
446 return this.counters.keySet();
447 }
448
449 public Map<Tuple, Timeline<Timestamp>> asMap() {
450 return this.timelines;
451 }
452
453 public Timeline<Timestamp> get(final ITuple tuple) {
454 return this.timelines.get(tuple);
455 }
456
457 @Override
458 public void clear() {
459 this.counters.clear();
460 this.timelines.clear();
461 if (this.foldingState != null) {
462 this.foldingState.clear();
463 }
464 }
465
466 public boolean containsKey(final ITuple tuple) {
467 return this.counters.containsKey(tuple);
468 }
469
470 @Override
471 public String toString() {
472 return this.counters + "\n" + this.timelines + "\n" + this.foldingState + "\n";
473 }
474
475 protected static final class CumulativeCounter {
476 protected int diff;
477 protected int cumulative;
478
479 protected CumulativeCounter(final int diff, final int cumulative) {
480 this.diff = diff;
481 this.cumulative = cumulative;
482 }
483
484 @Override
485 public String toString() {
486 return "{diff=" + this.diff + ", cumulative=" + this.cumulative + "}";
487 }
488
489 }
490
491 protected static final class FoldingState {
492 protected final int diff;
493
494 protected FoldingState(final int diff) {
495 this.diff = diff;
496 }
497
498 @Override
499 public String toString() {
500 return "{diff=" + this.diff + "}";
501 }
502
503 /**
504 * The returned result will never be null, even if the resulting diff is zero.
505 */
506 public FoldingState merge(final FoldingState that) {
507 Preconditions.checkArgument(that != null);
508 return new FoldingState(this.diff + that.diff);
509 }
510
511 }
512
513 protected enum SignChange {
514 BECAME_POSITIVE, BECAME_ZERO, IRRELEVANT;
515 }
516
517}