diff options
author | 2023-09-14 19:29:36 +0200 | |
---|---|---|
committer | 2023-09-14 19:29:36 +0200 | |
commit | 98ed3b6db5f4e51961a161050cc31c66015116e8 (patch) | |
tree | 8bfd6d9bc8d6ed23b9eb0f889dd40b6c24fe8f92 /subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject | |
parent | Merge pull request #38 from nagilooh/design-space-exploration (diff) | |
parent | Merge remote-tracking branch 'upstream/main' into partial-interpretation (diff) | |
download | refinery-98ed3b6db5f4e51961a161050cc31c66015116e8.tar.gz refinery-98ed3b6db5f4e51961a161050cc31c66015116e8.tar.zst refinery-98ed3b6db5f4e51961a161050cc31c66015116e8.zip |
Merge pull request #39 from kris7t/partial-interpretation
Implement partial interpretation based model generation
Diffstat (limited to 'subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject')
9 files changed, 559 insertions, 0 deletions
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java new file mode 100644 index 00000000..d8db4ec4 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | import tools.refinery.store.representation.TruthValue; | ||
14 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
15 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | public class EqualsRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> { | ||
19 | private final Interpretation<CardinalityInterval> countInterpretation; | ||
20 | |||
21 | private EqualsRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
22 | Symbol<CardinalityInterval> countSymbol) { | ||
23 | super(adapter, partialSymbol); | ||
24 | countInterpretation = adapter.getModel().getInterpretation(countSymbol); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean merge(Tuple key, TruthValue value) { | ||
29 | if (value == TruthValue.UNKNOWN) { | ||
30 | return true; | ||
31 | } | ||
32 | if (value == TruthValue.ERROR) { | ||
33 | return false; | ||
34 | } | ||
35 | int left = key.get(0); | ||
36 | int right = key.get(1); | ||
37 | boolean isDiagonal = left == right; | ||
38 | if (isDiagonal && value == TruthValue.FALSE) { | ||
39 | return false; | ||
40 | } | ||
41 | if (!isDiagonal) { | ||
42 | return !value.may(); | ||
43 | } | ||
44 | if (value != TruthValue.TRUE) { | ||
45 | throw new IllegalArgumentException("Unknown TruthValue: " + value); | ||
46 | } | ||
47 | // {@code isDiagonal} is true, so this could be {@code left} or {@code right}. | ||
48 | var unaryKey = Tuple.of(left); | ||
49 | var currentCount = countInterpretation.get(unaryKey); | ||
50 | if (currentCount == null) { | ||
51 | return false; | ||
52 | } | ||
53 | var newCount = currentCount.meet(CardinalityIntervals.LONE); | ||
54 | if (newCount.isEmpty()) { | ||
55 | return false; | ||
56 | } | ||
57 | countInterpretation.put(unaryKey, newCount); | ||
58 | return true; | ||
59 | } | ||
60 | |||
61 | public static Factory<TruthValue, Boolean> of(Symbol<CardinalityInterval> countSymbol) { | ||
62 | return (adapter, partialSymbol) -> new EqualsRefiner(adapter, partialSymbol, countSymbol); | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java new file mode 100644 index 00000000..61b9488c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java | |||
@@ -0,0 +1,85 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Query; | ||
9 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
10 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
11 | import tools.refinery.store.query.literal.CallLiteral; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.view.AnySymbolView; | ||
15 | import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter; | ||
16 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
17 | import tools.refinery.store.reasoning.literal.Modality; | ||
18 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
19 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
20 | |||
21 | import java.util.List; | ||
22 | import java.util.Set; | ||
23 | |||
24 | import static tools.refinery.store.query.literal.Literals.check; | ||
25 | import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant; | ||
26 | import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.lessEq; | ||
27 | |||
28 | class EqualsRelationRewriter extends QueryBasedRelationRewriter { | ||
29 | private EqualsRelationRewriter(RelationalQuery may, RelationalQuery must) { | ||
30 | super(may, must, may, may); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, | ||
35 | Modality modality, Concreteness concreteness) { | ||
36 | if (!(literal instanceof CallLiteral callLiteral)) { | ||
37 | return super.rewriteLiteral(positiveVariables, literal, modality, concreteness); | ||
38 | } | ||
39 | var left = callLiteral.getArguments().get(0).asNodeVariable(); | ||
40 | var right = callLiteral.getArguments().get(1).asNodeVariable(); | ||
41 | boolean useMay = modality == Modality.MAY || concreteness == Concreteness.CANDIDATE; | ||
42 | return switch (callLiteral.getPolarity()) { | ||
43 | case POSITIVE, TRANSITIVE -> { | ||
44 | if (useMay) { | ||
45 | if (positiveVariables.contains(left) || positiveVariables.contains(right)) { | ||
46 | // No need to enumerate arguments if at least one is already bound, since they will be unified. | ||
47 | yield List.of(left.isEquivalent(right)); | ||
48 | } else { | ||
49 | yield List.of( | ||
50 | left.isEquivalent(right), | ||
51 | getMay().call(left, right) | ||
52 | ); | ||
53 | } | ||
54 | } else { | ||
55 | yield List.of( | ||
56 | left.isEquivalent(right), | ||
57 | getMust().call(left, right) | ||
58 | ); | ||
59 | } | ||
60 | } | ||
61 | case NEGATIVE -> { | ||
62 | if (useMay) { | ||
63 | yield List.of(left.notEquivalent(right)); | ||
64 | } else { | ||
65 | yield super.rewriteLiteral(positiveVariables, literal, modality, concreteness); | ||
66 | } | ||
67 | } | ||
68 | }; | ||
69 | } | ||
70 | |||
71 | public static EqualsRelationRewriter of(AnySymbolView upperCardinalityView) { | ||
72 | var may = Query.of("equals#may", (builder, p1, p2) -> builder | ||
73 | .clause( | ||
74 | p1.isEquivalent(p2), | ||
75 | upperCardinalityView.call(p1, Variable.of(UpperCardinality.class)) | ||
76 | )); | ||
77 | var must = Query.of("equals#must", (builder, p1, p2) -> builder | ||
78 | .clause(UpperCardinality.class, upper -> List.of( | ||
79 | p1.isEquivalent(p2), | ||
80 | upperCardinalityView.call(p1, upper), | ||
81 | check(lessEq(upper, constant(UpperCardinalities.ONE))) | ||
82 | ))); | ||
83 | return new EqualsRelationRewriter(may, must); | ||
84 | } | ||
85 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java new file mode 100644 index 00000000..f134fe92 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java | |||
@@ -0,0 +1,55 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | import tools.refinery.store.representation.TruthValue; | ||
14 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
15 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | public class ExistsRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> { | ||
19 | private final Interpretation<CardinalityInterval> countInterpretation; | ||
20 | |||
21 | private ExistsRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
22 | Symbol<CardinalityInterval> countSymbol) { | ||
23 | super(adapter, partialSymbol); | ||
24 | countInterpretation = adapter.getModel().getInterpretation(countSymbol); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean merge(Tuple key, TruthValue value) { | ||
29 | var currentCount = countInterpretation.get(key); | ||
30 | if (currentCount == null) { | ||
31 | return false; | ||
32 | } | ||
33 | CardinalityInterval newCount; | ||
34 | switch (value) { | ||
35 | case UNKNOWN -> { | ||
36 | return true; | ||
37 | } | ||
38 | case TRUE -> newCount = currentCount.meet(CardinalityIntervals.SOME); | ||
39 | case FALSE -> newCount = currentCount.meet(CardinalityIntervals.NONE); | ||
40 | case ERROR -> { | ||
41 | return false; | ||
42 | } | ||
43 | default -> throw new IllegalArgumentException("Unknown TruthValue: " + value); | ||
44 | } | ||
45 | if (newCount.isEmpty()) { | ||
46 | return false; | ||
47 | } | ||
48 | countInterpretation.put(key, newCount); | ||
49 | return true; | ||
50 | } | ||
51 | |||
52 | public static Factory<TruthValue, Boolean> of(Symbol<CardinalityInterval> countSymbol) { | ||
53 | return (adapter, partialSymbol) -> new ExistsRefiner(adapter, partialSymbol, countSymbol); | ||
54 | } | ||
55 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java new file mode 100644 index 00000000..9873888c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.query.term.Parameter; | ||
9 | import tools.refinery.store.query.view.AbstractFunctionView; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
12 | |||
13 | class LowerCardinalityView extends AbstractFunctionView<CardinalityInterval> { | ||
14 | public LowerCardinalityView(Symbol<CardinalityInterval> symbol) { | ||
15 | super(symbol, "lower", new Parameter(Integer.class)); | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | protected Object forwardMapValue(CardinalityInterval value) { | ||
20 | return value.lowerBound(); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java new file mode 100644 index 00000000..89918155 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java | |||
@@ -0,0 +1,135 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import org.jetbrains.annotations.NotNull; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
11 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
12 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
13 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
17 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
18 | import tools.refinery.store.tuple.Tuple; | ||
19 | |||
20 | import java.util.Arrays; | ||
21 | import java.util.HashMap; | ||
22 | import java.util.function.Function; | ||
23 | |||
24 | class MultiObjectInitializer implements PartialModelInitializer { | ||
25 | private final Symbol<CardinalityInterval> countSymbol; | ||
26 | |||
27 | public MultiObjectInitializer(Symbol<CardinalityInterval> countSymbol) { | ||
28 | this.countSymbol = countSymbol; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public void initialize(Model model, ModelSeed modelSeed) { | ||
33 | var intervals = initializeIntervals(model, modelSeed); | ||
34 | initializeExists(intervals, model, modelSeed); | ||
35 | initializeEquals(intervals, model, modelSeed); | ||
36 | var countInterpretation = model.getInterpretation(countSymbol); | ||
37 | var uniqueTable = new HashMap<CardinalityInterval, CardinalityInterval>(); | ||
38 | for (int i = 0; i < intervals.length; i++) { | ||
39 | var interval = intervals[i]; | ||
40 | if (interval.isEmpty()) { | ||
41 | throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, | ||
42 | "Inconsistent existence or equality for node " + i); | ||
43 | } | ||
44 | var uniqueInterval = uniqueTable.computeIfAbsent(intervals[i], Function.identity()); | ||
45 | countInterpretation.put(Tuple.of(i), uniqueInterval); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | @NotNull | ||
50 | private CardinalityInterval[] initializeIntervals(Model model, ModelSeed modelSeed) { | ||
51 | var intervals = new CardinalityInterval[modelSeed.getNodeCount()]; | ||
52 | if (modelSeed.containsSeed(MultiObjectTranslator.COUNT_SYMBOL)) { | ||
53 | Arrays.fill(intervals, CardinalityIntervals.ONE); | ||
54 | var cursor = modelSeed.getCursor(MultiObjectTranslator.COUNT_SYMBOL, CardinalityIntervals.ONE); | ||
55 | while (cursor.move()) { | ||
56 | model.checkCancelled(); | ||
57 | int i = cursor.getKey().get(0); | ||
58 | checkNodeId(intervals, i); | ||
59 | intervals[i] = cursor.getValue(); | ||
60 | } | ||
61 | } else { | ||
62 | Arrays.fill(intervals, CardinalityIntervals.SET); | ||
63 | if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL) || | ||
64 | !modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) { | ||
65 | throw new TranslationException(MultiObjectTranslator.COUNT_SYMBOL, | ||
66 | "Seed for %s and %s is required if there is no seed for %s".formatted( | ||
67 | ReasoningAdapter.EXISTS_SYMBOL, ReasoningAdapter.EQUALS_SYMBOL, | ||
68 | MultiObjectTranslator.COUNT_SYMBOL)); | ||
69 | } | ||
70 | } | ||
71 | return intervals; | ||
72 | } | ||
73 | |||
74 | private void initializeExists(CardinalityInterval[] intervals, Model model, ModelSeed modelSeed) { | ||
75 | if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL)) { | ||
76 | return; | ||
77 | } | ||
78 | var cursor = modelSeed.getCursor(ReasoningAdapter.EXISTS_SYMBOL, TruthValue.UNKNOWN); | ||
79 | while (cursor.move()) { | ||
80 | model.checkCancelled(); | ||
81 | int i = cursor.getKey().get(0); | ||
82 | checkNodeId(intervals, i); | ||
83 | switch (cursor.getValue()) { | ||
84 | case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.SOME); | ||
85 | case FALSE -> intervals[i] = intervals[i].meet(CardinalityIntervals.NONE); | ||
86 | case ERROR -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, | ||
87 | "Inconsistent existence for node " + i); | ||
88 | default -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, | ||
89 | "Invalid existence truth value %s for node %d".formatted(cursor.getValue(), i)); | ||
90 | } | ||
91 | } | ||
92 | } | ||
93 | |||
94 | private void initializeEquals(CardinalityInterval[] intervals, Model model, ModelSeed modelSeed) { | ||
95 | if (!modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) { | ||
96 | return; | ||
97 | } | ||
98 | var seed = modelSeed.getSeed(ReasoningAdapter.EQUALS_SYMBOL); | ||
99 | var cursor = seed.getCursor(TruthValue.FALSE, modelSeed.getNodeCount()); | ||
100 | while (cursor.move()) { | ||
101 | model.checkCancelled(); | ||
102 | var key = cursor.getKey(); | ||
103 | int i = key.get(0); | ||
104 | int otherIndex = key.get(1); | ||
105 | if (i != otherIndex) { | ||
106 | throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, | ||
107 | "Off-diagonal equivalence (%d, %d) is not permitted".formatted(i, otherIndex)); | ||
108 | } | ||
109 | checkNodeId(intervals, i); | ||
110 | switch (cursor.getValue()) { | ||
111 | case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.LONE); | ||
112 | case UNKNOWN -> { | ||
113 | // Nothing do to, {@code intervals} is initialized with unknown equality. | ||
114 | } | ||
115 | case ERROR -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, | ||
116 | "Inconsistent equality for node " + i); | ||
117 | default -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, | ||
118 | "Invalid equality truth value %s for node %d".formatted(cursor.getValue(), i)); | ||
119 | } | ||
120 | } | ||
121 | for (int i = 0; i < intervals.length; i++) { | ||
122 | model.checkCancelled(); | ||
123 | if (seed.get(Tuple.of(i, i)) == TruthValue.FALSE) { | ||
124 | throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Inconsistent equality for node " + i); | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | private void checkNodeId(CardinalityInterval[] intervals, int nodeId) { | ||
130 | if (nodeId < 0 || nodeId >= intervals.length) { | ||
131 | throw new IllegalArgumentException("Expected node id %d to be lower than model size %d" | ||
132 | .formatted(nodeId, intervals.length)); | ||
133 | } | ||
134 | } | ||
135 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java new file mode 100644 index 00000000..e48934d8 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java | |||
@@ -0,0 +1,45 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
11 | import tools.refinery.store.representation.Symbol; | ||
12 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
13 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
14 | import tools.refinery.store.tuple.Tuple; | ||
15 | |||
16 | class MultiObjectStorageRefiner implements StorageRefiner { | ||
17 | private final Interpretation<CardinalityInterval> countInterpretation; | ||
18 | |||
19 | public MultiObjectStorageRefiner(Symbol<CardinalityInterval> countSymbol, Model model) { | ||
20 | countInterpretation = model.getInterpretation(countSymbol); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public boolean split(int parentNode, int childNode) { | ||
25 | var parentKey = Tuple.of(parentNode); | ||
26 | var parentCount = countInterpretation.get(parentKey); | ||
27 | if (parentCount == null) { | ||
28 | return false; | ||
29 | } | ||
30 | var newParentCount = parentCount.take(1); | ||
31 | if (newParentCount.isEmpty()) { | ||
32 | return false; | ||
33 | } | ||
34 | var childKey = Tuple.of(childNode); | ||
35 | countInterpretation.put(parentKey, newParentCount); | ||
36 | countInterpretation.put(childKey, CardinalityIntervals.ONE); | ||
37 | return true; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public boolean cleanup(int nodeToDelete) { | ||
42 | var previousCount = countInterpretation.put(Tuple.of(nodeToDelete), null); | ||
43 | return previousCount.lowerBound() == 0; | ||
44 | } | ||
45 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java new file mode 100644 index 00000000..97fda9d5 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java | |||
@@ -0,0 +1,107 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.dse.propagation.PropagationBuilder; | ||
9 | import tools.refinery.store.dse.transition.Rule; | ||
10 | import tools.refinery.store.dse.transition.objectives.Criteria; | ||
11 | import tools.refinery.store.dse.transition.objectives.Objectives; | ||
12 | import tools.refinery.store.model.ModelStoreBuilder; | ||
13 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
14 | import tools.refinery.store.query.dnf.Query; | ||
15 | import tools.refinery.store.query.literal.Literals; | ||
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.query.term.int_.IntTerms; | ||
18 | import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms; | ||
19 | import tools.refinery.store.query.view.AnySymbolView; | ||
20 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
21 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
22 | import tools.refinery.store.reasoning.actions.PartialActionLiterals; | ||
23 | import tools.refinery.store.reasoning.representation.PartialFunction; | ||
24 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
25 | import tools.refinery.store.reasoning.translator.RoundingMode; | ||
26 | import tools.refinery.store.representation.Symbol; | ||
27 | import tools.refinery.store.representation.cardinality.CardinalityDomain; | ||
28 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
29 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
30 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
31 | |||
32 | import java.util.List; | ||
33 | |||
34 | import static tools.refinery.store.query.literal.Literals.check; | ||
35 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
36 | |||
37 | public class MultiObjectTranslator implements ModelStoreConfiguration { | ||
38 | public static final Symbol<CardinalityInterval> COUNT_STORAGE = Symbol.of("COUNT", 1, CardinalityInterval.class, | ||
39 | null); | ||
40 | public static final AnySymbolView LOWER_CARDINALITY_VIEW = new LowerCardinalityView(COUNT_STORAGE); | ||
41 | public static final AnySymbolView UPPER_CARDINALITY_VIEW = new UpperCardinalityView(COUNT_STORAGE); | ||
42 | public static final AnySymbolView MULTI_VIEW = new MultiView(COUNT_STORAGE); | ||
43 | public static final PartialFunction<CardinalityInterval, Integer> COUNT_SYMBOL = new PartialFunction<>("COUNT", 1, | ||
44 | CardinalityDomain.INSTANCE); | ||
45 | |||
46 | @Override | ||
47 | public void apply(ModelStoreBuilder storeBuilder) { | ||
48 | storeBuilder.symbol(COUNT_STORAGE); | ||
49 | |||
50 | var aboveLowerBound = Query.of("count#aboveLowerBound", Integer.class, (builder, node, output) -> builder | ||
51 | .clause( | ||
52 | MULTI_VIEW.call(node), | ||
53 | LOWER_CARDINALITY_VIEW.call(node, output), | ||
54 | check(greater(output, IntTerms.constant(0))) | ||
55 | )); | ||
56 | var missingCardinality = Query.of("count#missing", Integer.class, (builder, output) -> builder | ||
57 | .clause( | ||
58 | output.assign(aboveLowerBound.aggregate(INT_SUM, Variable.of())) | ||
59 | )); | ||
60 | |||
61 | storeBuilder.with(PartialRelationTranslator.of(ReasoningAdapter.EXISTS_SYMBOL) | ||
62 | .may(Query.of("exists#may", (builder, p1) -> builder | ||
63 | .clause(UpperCardinality.class, upper -> List.of( | ||
64 | UPPER_CARDINALITY_VIEW.call(p1, upper), | ||
65 | check(UpperCardinalityTerms.greaterEq(upper, | ||
66 | UpperCardinalityTerms.constant(UpperCardinalities.ONE))) | ||
67 | )))) | ||
68 | .must(Query.of("exists#must", (builder, p1) -> builder | ||
69 | .clause(Integer.class, lower -> List.of( | ||
70 | LOWER_CARDINALITY_VIEW.call(p1, lower), | ||
71 | check(greaterEq(lower, constant(1))) | ||
72 | )))) | ||
73 | .candidate(Query.of("exists#candidate", (builder, p1) -> builder | ||
74 | .clause( | ||
75 | LOWER_CARDINALITY_VIEW.call(p1, Variable.of(Integer.class)), | ||
76 | Literals.not(MULTI_VIEW.call(p1)) | ||
77 | ))) | ||
78 | .roundingMode(RoundingMode.PREFER_FALSE) | ||
79 | .refiner(ExistsRefiner.of(COUNT_STORAGE)) | ||
80 | .exclude(null) | ||
81 | .accept(Criteria.whenNoMatch(aboveLowerBound)) | ||
82 | .objective(Objectives.value(missingCardinality))); | ||
83 | |||
84 | storeBuilder.with(PartialRelationTranslator.of(ReasoningAdapter.EQUALS_SYMBOL) | ||
85 | .rewriter(EqualsRelationRewriter.of(UPPER_CARDINALITY_VIEW)) | ||
86 | .refiner(EqualsRefiner.of(COUNT_STORAGE)) | ||
87 | .exclude(null) | ||
88 | .accept(null) | ||
89 | .objective(null)); | ||
90 | |||
91 | var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class); | ||
92 | reasoningBuilder.initializer(new MultiObjectInitializer(COUNT_STORAGE)); | ||
93 | reasoningBuilder.storageRefiner(COUNT_STORAGE, MultiObjectStorageRefiner::new); | ||
94 | |||
95 | storeBuilder.tryGetAdapter(PropagationBuilder.class) | ||
96 | .ifPresent(propagationBuilder -> propagationBuilder.rule( | ||
97 | Rule.of("exists#cleanup", (builder, node) -> builder | ||
98 | .clause(UpperCardinality.class, upper -> List.of( | ||
99 | UPPER_CARDINALITY_VIEW.call(node, upper), | ||
100 | check(UpperCardinalityTerms.less(upper, | ||
101 | UpperCardinalityTerms.constant(UpperCardinalities.ONE))) | ||
102 | )) | ||
103 | .action( | ||
104 | PartialActionLiterals.cleanup(node) | ||
105 | )))); | ||
106 | } | ||
107 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java new file mode 100644 index 00000000..498bcd83 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.query.view.TuplePreservingView; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | class MultiView extends TuplePreservingView<CardinalityInterval> { | ||
15 | protected MultiView(Symbol<CardinalityInterval> symbol) { | ||
16 | super(symbol, "multi"); | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | protected boolean doFilter(Tuple key, CardinalityInterval value) { | ||
21 | return !CardinalityIntervals.ONE.equals(value); | ||
22 | } | ||
23 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java new file mode 100644 index 00000000..6be6ae1b --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import tools.refinery.store.query.term.Parameter; | ||
9 | import tools.refinery.store.query.view.AbstractFunctionView; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
12 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
13 | |||
14 | class UpperCardinalityView extends AbstractFunctionView<CardinalityInterval> { | ||
15 | public UpperCardinalityView(Symbol<CardinalityInterval> symbol) { | ||
16 | super(symbol, "upper", new Parameter(UpperCardinality.class)); | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | protected Object forwardMapValue(CardinalityInterval value) { | ||
21 | return value.upperBound(); | ||
22 | } | ||
23 | } | ||