diff options
Diffstat (limited to 'subprojects/store-reasoning/src')
127 files changed, 7813 insertions, 1277 deletions
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java deleted file mode 100644 index d3a216d8..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | public enum MergeResult { | ||
9 | UNCHANGED, | ||
10 | REFINED, | ||
11 | REJECTED; | ||
12 | |||
13 | public MergeResult andAlso(MergeResult other) { | ||
14 | return switch (this) { | ||
15 | case UNCHANGED -> other; | ||
16 | case REFINED -> other == REJECTED ? REJECTED : REFINED; | ||
17 | case REJECTED -> REJECTED; | ||
18 | }; | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java deleted file mode 100644 index 4140d640..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
9 | import tools.refinery.store.map.Cursor; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation { | ||
13 | @Override | ||
14 | PartialSymbol<A, C> getPartialSymbol(); | ||
15 | |||
16 | A get(Tuple key); | ||
17 | |||
18 | Cursor<Tuple, A> getAll(); | ||
19 | |||
20 | MergeResult merge(Tuple key, A value); | ||
21 | |||
22 | C getConcrete(Tuple key); | ||
23 | |||
24 | Cursor<Tuple, C> getAllConcrete(); | ||
25 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java index 6d5d6f89..7f0ef8b4 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java | |||
@@ -5,26 +5,51 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning; | 6 | package tools.refinery.store.reasoning; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | ||
8 | import tools.refinery.store.adapter.ModelAdapter; | 9 | import tools.refinery.store.adapter.ModelAdapter; |
9 | import tools.refinery.store.query.resultset.ResultSet; | 10 | import tools.refinery.store.reasoning.internal.ReasoningBuilderImpl; |
10 | import tools.refinery.store.query.dnf.Dnf; | 11 | import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation; |
12 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
14 | import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner; | ||
15 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | 16 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; |
12 | import tools.refinery.store.reasoning.representation.PartialRelation; | 17 | import tools.refinery.store.reasoning.representation.PartialRelation; |
13 | import tools.refinery.store.reasoning.representation.PartialSymbol; | 18 | import tools.refinery.store.reasoning.representation.PartialSymbol; |
19 | import tools.refinery.store.tuple.Tuple1; | ||
14 | 20 | ||
15 | public interface ReasoningAdapter extends ModelAdapter { | 21 | public interface ReasoningAdapter extends ModelAdapter { |
16 | PartialRelation EXISTS = new PartialRelation("exists", 1); | 22 | PartialRelation EXISTS_SYMBOL = PartialSymbol.of("exists", 1); |
23 | PartialRelation EQUALS_SYMBOL = PartialSymbol.of("equals", 2); | ||
17 | 24 | ||
18 | @Override | 25 | @Override |
19 | ReasoningStoreAdapter getStoreAdapter(); | 26 | ReasoningStoreAdapter getStoreAdapter(); |
20 | 27 | ||
21 | default AnyPartialInterpretation getPartialInterpretation(AnyPartialSymbol partialSymbol) { | 28 | default AnyPartialInterpretation getPartialInterpretation(Concreteness concreteness, |
22 | // Cast to disambiguate overloads. | 29 | AnyPartialSymbol partialSymbol) { |
23 | var typedPartialSymbol = (PartialSymbol<?, ?>) partialSymbol; | 30 | return getPartialInterpretation(concreteness, (PartialSymbol<?, ?>) partialSymbol); |
24 | return getPartialInterpretation(typedPartialSymbol); | ||
25 | } | 31 | } |
26 | 32 | ||
27 | <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol); | 33 | <A, C> PartialInterpretation<A, C> getPartialInterpretation(Concreteness concreteness, |
34 | PartialSymbol<A, C> partialSymbol); | ||
28 | 35 | ||
29 | ResultSet<Boolean> getLiftedResultSet(Dnf query); | 36 | default AnyPartialInterpretationRefiner getRefiner(AnyPartialSymbol partialSymbol) { |
37 | return getRefiner((PartialSymbol<?, ?>) partialSymbol); | ||
38 | } | ||
39 | |||
40 | <A, C> PartialInterpretationRefiner<A, C> getRefiner(PartialSymbol<A, C> partialSymbol); | ||
41 | |||
42 | @Nullable | ||
43 | Tuple1 split(int parentMultiObject); | ||
44 | |||
45 | @Nullable | ||
46 | Tuple1 focus(int parentObject); | ||
47 | |||
48 | boolean cleanup(int nodeToDelete); | ||
49 | |||
50 | int getNodeCount(); | ||
51 | |||
52 | static ReasoningBuilder builder() { | ||
53 | return new ReasoningBuilderImpl(); | ||
54 | } | ||
30 | } | 55 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java index d3a337e8..79bce33e 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java | |||
@@ -6,27 +6,54 @@ | |||
6 | package tools.refinery.store.reasoning; | 6 | package tools.refinery.store.reasoning; |
7 | 7 | ||
8 | import tools.refinery.store.adapter.ModelAdapterBuilder; | 8 | import tools.refinery.store.adapter.ModelAdapterBuilder; |
9 | import tools.refinery.store.dse.transition.objectives.Objective; | ||
9 | import tools.refinery.store.model.ModelStore; | 10 | import tools.refinery.store.model.ModelStore; |
10 | import tools.refinery.store.reasoning.literal.Modality; | ||
11 | import tools.refinery.store.query.dnf.Dnf; | 11 | import tools.refinery.store.query.dnf.Dnf; |
12 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
13 | import tools.refinery.store.query.dnf.Query; | ||
14 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
15 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
16 | import tools.refinery.store.reasoning.literal.Modality; | ||
17 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
18 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
19 | import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
12 | 21 | ||
13 | import java.util.Collection; | 22 | import java.util.Collection; |
14 | import java.util.List; | 23 | import java.util.List; |
15 | 24 | ||
16 | @SuppressWarnings("UnusedReturnValue") | 25 | @SuppressWarnings("UnusedReturnValue") |
17 | public interface ReasoningBuilder extends ModelAdapterBuilder { | 26 | public interface ReasoningBuilder extends ModelAdapterBuilder { |
18 | default ReasoningBuilder liftedQueries(Dnf... liftedQueries) { | 27 | ReasoningBuilder requiredInterpretations(Collection<Concreteness> requiredInterpretations); |
19 | return liftedQueries(List.of(liftedQueries)); | 28 | |
29 | default ReasoningBuilder requiredInterpretations(Concreteness... requiredInterpretations) { | ||
30 | return requiredInterpretations(List.of(requiredInterpretations)); | ||
20 | } | 31 | } |
21 | 32 | ||
22 | default ReasoningBuilder liftedQueries(Collection<Dnf> liftedQueries) { | 33 | ReasoningBuilder partialSymbol(AnyPartialSymbolTranslator translator); |
23 | liftedQueries.forEach(this::liftedQuery); | 34 | |
35 | <T> ReasoningBuilder storageRefiner(Symbol<T> symbol, StorageRefiner.Factory<T> refiner); | ||
36 | |||
37 | ReasoningBuilder initializer(PartialModelInitializer initializer); | ||
38 | |||
39 | ReasoningBuilder objective(Objective objective); | ||
40 | |||
41 | default ReasoningBuilder objectives(Objective... objectives) { | ||
42 | return objectives(List.of(objectives)); | ||
43 | } | ||
44 | |||
45 | default ReasoningBuilder objectives(Collection<Objective> objectives) { | ||
46 | objectives.forEach(this::objective); | ||
24 | return this; | 47 | return this; |
25 | } | 48 | } |
26 | 49 | ||
27 | ReasoningBuilder liftedQuery(Dnf liftedQuery); | 50 | <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query); |
51 | |||
52 | RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query); | ||
53 | |||
54 | <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query); | ||
28 | 55 | ||
29 | Dnf lift(Modality modality, Dnf query); | 56 | Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf); |
30 | 57 | ||
31 | @Override | 58 | @Override |
32 | ReasoningStoreAdapter build(ModelStore store); | 59 | ReasoningStoreAdapter build(ModelStore store); |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java index c9795255..fe3cc3ea 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java | |||
@@ -7,15 +7,21 @@ package tools.refinery.store.reasoning; | |||
7 | 7 | ||
8 | import tools.refinery.store.adapter.ModelStoreAdapter; | 8 | import tools.refinery.store.adapter.ModelStoreAdapter; |
9 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
10 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | 11 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; |
11 | import tools.refinery.store.query.dnf.Dnf; | 12 | import tools.refinery.store.reasoning.seed.ModelSeed; |
12 | 13 | ||
13 | import java.util.Collection; | 14 | import java.util.Collection; |
15 | import java.util.Set; | ||
14 | 16 | ||
15 | public interface ReasoningStoreAdapter extends ModelStoreAdapter { | 17 | public interface ReasoningStoreAdapter extends ModelStoreAdapter { |
16 | Collection<AnyPartialSymbol> getPartialSymbols(); | 18 | Collection<AnyPartialSymbol> getPartialSymbols(); |
17 | 19 | ||
18 | Collection<Dnf> getLiftedQueries(); | 20 | Collection<AnyPartialSymbol> getRefinablePartialSymbols(); |
21 | |||
22 | Set<Concreteness> getSupportedInterpretations(); | ||
23 | |||
24 | Model createInitialModel(ModelSeed modelSeed); | ||
19 | 25 | ||
20 | @Override | 26 | @Override |
21 | ReasoningAdapter createModelAdapter(Model model); | 27 | ReasoningAdapter createModelAdapter(Model model); |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/CleanupActionLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/CleanupActionLiteral.java new file mode 100644 index 00000000..62c35cee --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/CleanupActionLiteral.java | |||
@@ -0,0 +1,43 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.actions; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.actions.AbstractActionLiteral; | ||
9 | import tools.refinery.store.dse.transition.actions.BoundActionLiteral; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | import tools.refinery.store.tuple.Tuple; | ||
14 | |||
15 | import java.util.List; | ||
16 | |||
17 | public class CleanupActionLiteral extends AbstractActionLiteral { | ||
18 | private final NodeVariable node; | ||
19 | |||
20 | public CleanupActionLiteral(NodeVariable node) { | ||
21 | this.node = node; | ||
22 | } | ||
23 | |||
24 | public NodeVariable getNode() { | ||
25 | return node; | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public List<NodeVariable> getInputVariables() { | ||
30 | return List.of(node); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public List<NodeVariable> getOutputVariables() { | ||
35 | return List.of(); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public BoundActionLiteral bindToModel(Model model) { | ||
40 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
41 | return tuple -> reasoningAdapter.cleanup(tuple.get(0)) ? Tuple.of() : null; | ||
42 | } | ||
43 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java new file mode 100644 index 00000000..5a6f22d2 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java | |||
@@ -0,0 +1,48 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.actions; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.actions.AbstractActionLiteral; | ||
9 | import tools.refinery.store.dse.transition.actions.BoundActionLiteral; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | |||
14 | import java.util.List; | ||
15 | |||
16 | public class FocusActionLiteral extends AbstractActionLiteral { | ||
17 | private final NodeVariable parentNode; | ||
18 | private final NodeVariable childNode; | ||
19 | |||
20 | public FocusActionLiteral(NodeVariable parentNode, NodeVariable childNode) { | ||
21 | this.parentNode = parentNode; | ||
22 | this.childNode = childNode; | ||
23 | } | ||
24 | |||
25 | public NodeVariable getParentNode() { | ||
26 | return parentNode; | ||
27 | } | ||
28 | |||
29 | public NodeVariable getChildNode() { | ||
30 | return childNode; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public List<NodeVariable> getInputVariables() { | ||
35 | return List.of(parentNode); | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public List<NodeVariable> getOutputVariables() { | ||
40 | return List.of(childNode); | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public BoundActionLiteral bindToModel(Model model) { | ||
45 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
46 | return tuple -> reasoningAdapter.focus(tuple.get(0)); | ||
47 | } | ||
48 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java new file mode 100644 index 00000000..8d0d7961 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.actions; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.actions.AbstractActionLiteral; | ||
9 | import tools.refinery.store.dse.transition.actions.BoundActionLiteral; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
14 | import tools.refinery.store.tuple.Tuple; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | public class MergeActionLiteral<A, C> extends AbstractActionLiteral { | ||
19 | private final PartialSymbol<A, C> partialSymbol; | ||
20 | private final List<NodeVariable> parameters; | ||
21 | private final A value; | ||
22 | |||
23 | public MergeActionLiteral(PartialSymbol<A, C> partialSymbol, A value, List<NodeVariable> parameters) { | ||
24 | if (partialSymbol.arity() != parameters.size()) { | ||
25 | throw new IllegalArgumentException("Expected %d parameters for partial symbol %s, got %d instead" | ||
26 | .formatted(partialSymbol.arity(), partialSymbol, parameters.size())); | ||
27 | } | ||
28 | this.partialSymbol = partialSymbol; | ||
29 | this.parameters = parameters; | ||
30 | this.value = value; | ||
31 | } | ||
32 | |||
33 | public PartialSymbol<A, C> getPartialSymbol() { | ||
34 | return partialSymbol; | ||
35 | } | ||
36 | |||
37 | public List<NodeVariable> getParameters() { | ||
38 | return parameters; | ||
39 | } | ||
40 | |||
41 | public A getValue() { | ||
42 | return value; | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public List<NodeVariable> getInputVariables() { | ||
47 | return getParameters(); | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public List<NodeVariable> getOutputVariables() { | ||
52 | return List.of(); | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public BoundActionLiteral bindToModel(Model model) { | ||
57 | var refiner = model.getAdapter(ReasoningAdapter.class).getRefiner(partialSymbol); | ||
58 | return tuple -> refiner.merge(tuple, value) ? Tuple.of() : null; | ||
59 | } | ||
60 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java new file mode 100644 index 00000000..f36fde44 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java | |||
@@ -0,0 +1,42 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.actions; | ||
7 | |||
8 | import tools.refinery.store.query.term.NodeVariable; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.representation.TruthValue; | ||
12 | |||
13 | import java.util.List; | ||
14 | |||
15 | public final class PartialActionLiterals { | ||
16 | private PartialActionLiterals() { | ||
17 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
18 | } | ||
19 | |||
20 | public static <A, C> MergeActionLiteral<A, C> merge(PartialSymbol<A, C> partialSymbol, A value, | ||
21 | NodeVariable... parameters) { | ||
22 | return new MergeActionLiteral<>(partialSymbol, value, List.of(parameters)); | ||
23 | } | ||
24 | |||
25 | public static MergeActionLiteral<TruthValue, Boolean> add(PartialRelation partialRelation, | ||
26 | NodeVariable... parameters) { | ||
27 | return merge(partialRelation, TruthValue.TRUE, parameters); | ||
28 | } | ||
29 | |||
30 | public static MergeActionLiteral<TruthValue, Boolean> remove(PartialRelation partialRelation, | ||
31 | NodeVariable... parameters) { | ||
32 | return merge(partialRelation, TruthValue.FALSE, parameters); | ||
33 | } | ||
34 | |||
35 | public static FocusActionLiteral focus(NodeVariable parent, NodeVariable child) { | ||
36 | return new FocusActionLiteral(parent, child); | ||
37 | } | ||
38 | |||
39 | public static CleanupActionLiteral cleanup(NodeVariable node) { | ||
40 | return new CleanupActionLiteral(node); | ||
41 | } | ||
42 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java new file mode 100644 index 00000000..40993235 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java | |||
@@ -0,0 +1,223 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.internal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.DnfBuilder; | ||
11 | import tools.refinery.store.query.dnf.DnfClause; | ||
12 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
13 | import tools.refinery.store.query.literal.AbstractCountLiteral; | ||
14 | import tools.refinery.store.query.literal.CallPolarity; | ||
15 | import tools.refinery.store.query.literal.Literal; | ||
16 | import tools.refinery.store.query.term.Aggregator; | ||
17 | import tools.refinery.store.query.term.ConstantTerm; | ||
18 | import tools.refinery.store.query.term.Term; | ||
19 | import tools.refinery.store.query.term.Variable; | ||
20 | import tools.refinery.store.query.term.int_.IntTerms; | ||
21 | import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms; | ||
22 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
23 | import tools.refinery.store.reasoning.literal.*; | ||
24 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
25 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
26 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
27 | |||
28 | import java.util.*; | ||
29 | import java.util.function.BinaryOperator; | ||
30 | |||
31 | class PartialClauseRewriter { | ||
32 | private final PartialQueryRewriter rewriter; | ||
33 | private final List<Literal> completedLiterals = new ArrayList<>(); | ||
34 | private final Deque<Literal> workList = new ArrayDeque<>(); | ||
35 | private final Set<Variable> positiveVariables = new LinkedHashSet<>(); | ||
36 | private final Set<Variable> unmodifiablePositiveVariables = Collections.unmodifiableSet(positiveVariables); | ||
37 | |||
38 | public PartialClauseRewriter(PartialQueryRewriter rewriter) { | ||
39 | this.rewriter = rewriter; | ||
40 | } | ||
41 | |||
42 | public List<Literal> rewriteClause(DnfClause clause) { | ||
43 | workList.addAll(clause.literals()); | ||
44 | while (!workList.isEmpty()) { | ||
45 | var literal = workList.removeFirst(); | ||
46 | rewrite(literal); | ||
47 | } | ||
48 | return completedLiterals; | ||
49 | } | ||
50 | |||
51 | private void rewrite(Literal literal) { | ||
52 | if (!(literal instanceof AbstractCallLiteral callLiteral)) { | ||
53 | markAsDone(literal); | ||
54 | return; | ||
55 | } | ||
56 | if (callLiteral instanceof CountLowerBoundLiteral countLowerBoundLiteral) { | ||
57 | rewriteCountLowerBound(countLowerBoundLiteral); | ||
58 | return; | ||
59 | } | ||
60 | if (callLiteral instanceof CountUpperBoundLiteral countUpperBoundLiteral) { | ||
61 | rewriteCountUpperBound(countUpperBoundLiteral); | ||
62 | return; | ||
63 | } | ||
64 | if (callLiteral instanceof CountCandidateLowerBoundLiteral countCandidateLowerBoundLiteral) { | ||
65 | rewriteCountCandidateLowerBound(countCandidateLowerBoundLiteral); | ||
66 | return; | ||
67 | } | ||
68 | if (callLiteral instanceof CountCandidateUpperBoundLiteral countCandidateUpperBoundLiteral) { | ||
69 | rewriteCountCandidateUpperBound(countCandidateUpperBoundLiteral); | ||
70 | return; | ||
71 | } | ||
72 | var target = callLiteral.getTarget(); | ||
73 | if (target instanceof Dnf dnf) { | ||
74 | rewriteRecursively(callLiteral, dnf); | ||
75 | } else if (target instanceof ModalConstraint modalConstraint) { | ||
76 | var modality = modalConstraint.modality(); | ||
77 | var concreteness = modalConstraint.concreteness(); | ||
78 | var constraint = modalConstraint.constraint(); | ||
79 | if (constraint instanceof Dnf dnf) { | ||
80 | rewriteRecursively(callLiteral, modality, concreteness, dnf); | ||
81 | } else if (constraint instanceof PartialRelation partialRelation) { | ||
82 | rewrite(callLiteral, modality, concreteness, partialRelation); | ||
83 | } else { | ||
84 | throw new IllegalArgumentException("Cannot interpret modal constraint: " + modalConstraint); | ||
85 | } | ||
86 | } else { | ||
87 | markAsDone(literal); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | private void rewriteCountLowerBound(CountLowerBoundLiteral literal) { | ||
92 | rewritePartialCount(literal, "lower", Modality.MUST, MultiObjectTranslator.LOWER_CARDINALITY_VIEW, 1, | ||
93 | IntTerms::mul, IntTerms.INT_SUM); | ||
94 | } | ||
95 | |||
96 | private void rewriteCountUpperBound(CountUpperBoundLiteral literal) { | ||
97 | rewritePartialCount(literal, "upper", Modality.MAY, MultiObjectTranslator.UPPER_CARDINALITY_VIEW, | ||
98 | UpperCardinalities.ONE, UpperCardinalityTerms::mul, UpperCardinalityTerms.UPPER_CARDINALITY_SUM); | ||
99 | } | ||
100 | |||
101 | private <T> void rewritePartialCount(AbstractCountLiteral<T> literal, String name, Modality modality, | ||
102 | Constraint view, T one, BinaryOperator<Term<T>> mul, Aggregator<T, T> sum) { | ||
103 | var type = literal.getResultType(); | ||
104 | var countResult = computeCountVariables(literal, Concreteness.PARTIAL, name); | ||
105 | var builder = countResult.builder(); | ||
106 | var outputVariable = builder.parameter(type); | ||
107 | var variablesToCount = countResult.variablesToCount(); | ||
108 | |||
109 | var literals = new ArrayList<Literal>(); | ||
110 | literals.add(new ModalConstraint(modality, Concreteness.PARTIAL, literal.getTarget()) | ||
111 | .call(CallPolarity.POSITIVE, countResult.rewrittenArguments())); | ||
112 | switch (variablesToCount.size()) { | ||
113 | case 0 -> literals.add(outputVariable.assign(new ConstantTerm<>(type, one))); | ||
114 | case 1 -> literals.add(view.call(variablesToCount.get(0), | ||
115 | outputVariable)); | ||
116 | default -> { | ||
117 | var firstCount = Variable.of(type); | ||
118 | literals.add(view.call(variablesToCount.get(0), firstCount)); | ||
119 | int length = variablesToCount.size(); | ||
120 | Term<T> accumulator = firstCount; | ||
121 | for (int i = 1; i < length; i++) { | ||
122 | var countVariable = Variable.of(type); | ||
123 | literals.add(view.call(variablesToCount.get(i), countVariable)); | ||
124 | accumulator = mul.apply(accumulator, countVariable); | ||
125 | } | ||
126 | literals.add(outputVariable.assign(accumulator)); | ||
127 | } | ||
128 | } | ||
129 | builder.clause(literals); | ||
130 | |||
131 | var helperQuery = builder.build(); | ||
132 | var aggregationVariable = Variable.of(type); | ||
133 | var helperArguments = countResult.helperArguments(); | ||
134 | helperArguments.add(aggregationVariable); | ||
135 | workList.addFirst(literal.getResultVariable().assign(helperQuery.aggregateBy(aggregationVariable, sum, | ||
136 | helperArguments))); | ||
137 | } | ||
138 | |||
139 | private void rewriteCountCandidateLowerBound(CountCandidateLowerBoundLiteral literal) { | ||
140 | rewriteCandidateCount(literal, "lower", Modality.MAY); | ||
141 | } | ||
142 | |||
143 | private void rewriteCountCandidateUpperBound(CountCandidateUpperBoundLiteral literal) { | ||
144 | rewriteCandidateCount(literal, "upper", Modality.MUST); | ||
145 | } | ||
146 | |||
147 | private void rewriteCandidateCount(AbstractCountLiteral<Integer> literal, String name, Modality modality) { | ||
148 | var countResult = computeCountVariables(literal, Concreteness.CANDIDATE, name); | ||
149 | var builder = countResult.builder(); | ||
150 | |||
151 | var literals = new ArrayList<Literal>(); | ||
152 | literals.add(new ModalConstraint(modality, Concreteness.CANDIDATE, literal.getTarget()) | ||
153 | .call(CallPolarity.POSITIVE, countResult.rewrittenArguments())); | ||
154 | for (var variable : countResult.variablesToCount()) { | ||
155 | literals.add(new ModalConstraint(modality, Concreteness.CANDIDATE, ReasoningAdapter.EXISTS_SYMBOL) | ||
156 | .call(variable)); | ||
157 | } | ||
158 | builder.clause(literals); | ||
159 | |||
160 | var helperQuery = builder.build(); | ||
161 | workList.addFirst(literal.getResultVariable().assign(helperQuery.count(countResult.helperArguments()))); | ||
162 | } | ||
163 | |||
164 | private CountResult computeCountVariables(AbstractCountLiteral<?> literal, Concreteness concreteness, | ||
165 | String name) { | ||
166 | var target = literal.getTarget(); | ||
167 | int arity = target.arity(); | ||
168 | var parameters = target.getParameters(); | ||
169 | var literalArguments = literal.getArguments(); | ||
170 | var privateVariables = literal.getPrivateVariables(positiveVariables); | ||
171 | var builder = Dnf.builder("%s#%s#%s".formatted(target.name(), concreteness, name)); | ||
172 | var rewrittenArguments = new ArrayList<Variable>(parameters.size()); | ||
173 | var variablesToCount = new ArrayList<Variable>(); | ||
174 | var helperArguments = new ArrayList<Variable>(); | ||
175 | var literalToRewrittenArgumentMap = new HashMap<Variable, Variable>(); | ||
176 | for (int i = 0; i < arity; i++) { | ||
177 | var literalArgument = literalArguments.get(i); | ||
178 | var parameter = parameters.get(i); | ||
179 | var rewrittenArgument = literalToRewrittenArgumentMap.computeIfAbsent(literalArgument, key -> { | ||
180 | helperArguments.add(key); | ||
181 | var newArgument = builder.parameter(parameter); | ||
182 | if (privateVariables.contains(key)) { | ||
183 | variablesToCount.add(newArgument); | ||
184 | } | ||
185 | return newArgument; | ||
186 | }); | ||
187 | rewrittenArguments.add(rewrittenArgument); | ||
188 | } | ||
189 | return new CountResult(builder, rewrittenArguments, helperArguments, variablesToCount); | ||
190 | } | ||
191 | |||
192 | private void markAsDone(Literal literal) { | ||
193 | completedLiterals.add(literal); | ||
194 | positiveVariables.addAll(literal.getOutputVariables()); | ||
195 | } | ||
196 | |||
197 | private void rewriteRecursively(AbstractCallLiteral callLiteral, Modality modality, Concreteness concreteness, | ||
198 | Dnf dnf) { | ||
199 | var liftedDnf = rewriter.getLifter().lift(modality, concreteness, dnf); | ||
200 | rewriteRecursively(callLiteral, liftedDnf); | ||
201 | } | ||
202 | |||
203 | private void rewriteRecursively(AbstractCallLiteral callLiteral, Dnf dnf) { | ||
204 | var rewrittenDnf = rewriter.rewrite(dnf); | ||
205 | var rewrittenLiteral = callLiteral.withTarget(rewrittenDnf); | ||
206 | markAsDone(rewrittenLiteral); | ||
207 | } | ||
208 | |||
209 | private void rewrite(AbstractCallLiteral callLiteral, Modality modality, Concreteness concreteness, | ||
210 | PartialRelation partialRelation) { | ||
211 | var relationRewriter = rewriter.getRelationRewriter(partialRelation); | ||
212 | var literals = relationRewriter.rewriteLiteral( | ||
213 | unmodifiablePositiveVariables, callLiteral, modality, concreteness); | ||
214 | int length = literals.size(); | ||
215 | for (int i = length - 1; i >= 0; i--) { | ||
216 | workList.addFirst(literals.get(i)); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | private record CountResult(DnfBuilder builder, List<Variable> rewrittenArguments, List<Variable> helperArguments, | ||
221 | List<Variable> variablesToCount) { | ||
222 | } | ||
223 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java new file mode 100644 index 00000000..79cba263 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.internal; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.query.rewriter.AbstractRecursiveRewriter; | ||
10 | import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter; | ||
11 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
12 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
13 | |||
14 | import java.util.HashMap; | ||
15 | import java.util.Map; | ||
16 | |||
17 | class PartialQueryRewriter extends AbstractRecursiveRewriter { | ||
18 | private final DnfLifter lifter; | ||
19 | private final Map<PartialRelation, PartialRelationRewriter> relationRewriterMap = new HashMap<>(); | ||
20 | |||
21 | PartialQueryRewriter(DnfLifter lifter) { | ||
22 | this.lifter = lifter; | ||
23 | } | ||
24 | |||
25 | DnfLifter getLifter() { | ||
26 | return lifter; | ||
27 | } | ||
28 | |||
29 | PartialRelationRewriter getRelationRewriter(PartialRelation partialRelation) { | ||
30 | var rewriter = relationRewriterMap.get(partialRelation); | ||
31 | if (rewriter == null) { | ||
32 | throw new IllegalArgumentException("Do not know how to interpret partial relation: " + partialRelation); | ||
33 | } | ||
34 | return rewriter; | ||
35 | } | ||
36 | |||
37 | public void addRelationRewriter(PartialRelation partialRelation, PartialRelationRewriter interpreter) { | ||
38 | if (relationRewriterMap.put(partialRelation, interpreter) != null) { | ||
39 | throw new IllegalArgumentException("Duplicate partial relation: " + partialRelation); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | protected Dnf doRewrite(Dnf dnf) { | ||
45 | var builder = Dnf.builderFrom(dnf); | ||
46 | for (var clause : dnf.getClauses()) { | ||
47 | var clauseRewriter = new PartialClauseRewriter(this); | ||
48 | var rewrittenClauses = clauseRewriter.rewriteClause(clause); | ||
49 | builder.clause(rewrittenClauses); | ||
50 | } | ||
51 | return builder.build(); | ||
52 | } | ||
53 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java index 1bd3ad2e..bd16bdfa 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java | |||
@@ -5,20 +5,114 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.internal; | 6 | package tools.refinery.store.reasoning.internal; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
8 | import tools.refinery.store.model.Model; | 10 | import tools.refinery.store.model.Model; |
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | 11 | import tools.refinery.store.reasoning.ReasoningAdapter; |
10 | import tools.refinery.store.reasoning.PartialInterpretation; | 12 | import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation; |
13 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner; | ||
16 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
17 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
18 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
11 | import tools.refinery.store.reasoning.representation.PartialSymbol; | 19 | import tools.refinery.store.reasoning.representation.PartialSymbol; |
12 | import tools.refinery.store.query.dnf.Dnf; | 20 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; |
13 | import tools.refinery.store.query.resultset.ResultSet; | 21 | import tools.refinery.store.representation.Symbol; |
22 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
23 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
24 | import tools.refinery.store.tuple.Tuple; | ||
25 | import tools.refinery.store.tuple.Tuple1; | ||
14 | 26 | ||
15 | public class ReasoningAdapterImpl implements ReasoningAdapter { | 27 | import java.util.HashMap; |
28 | import java.util.Map; | ||
29 | |||
30 | class ReasoningAdapterImpl implements ReasoningAdapter { | ||
31 | static final Symbol<Integer> NODE_COUNT_SYMBOL = Symbol.of("MODEL_SIZE", 0, Integer.class, 0); | ||
16 | private final Model model; | 32 | private final Model model; |
17 | private final ReasoningStoreAdapterImpl storeAdapter; | 33 | private final ReasoningStoreAdapterImpl storeAdapter; |
34 | private final Map<AnyPartialSymbol, AnyPartialInterpretation>[] partialInterpretations; | ||
35 | private final Map<AnyPartialSymbol, AnyPartialInterpretationRefiner> refiners; | ||
36 | private final StorageRefiner[] storageRefiners; | ||
37 | private final Interpretation<Integer> nodeCountInterpretation; | ||
38 | private final Interpretation<CardinalityInterval> countInterpretation; | ||
18 | 39 | ||
19 | ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) { | 40 | ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) { |
20 | this.model = model; | 41 | this.model = model; |
21 | this.storeAdapter = storeAdapter; | 42 | this.storeAdapter = storeAdapter; |
43 | |||
44 | int concretenessLength = Concreteness.values().length; | ||
45 | // Creation of a generic array. | ||
46 | @SuppressWarnings({"unchecked", "squid:S1905"}) | ||
47 | var interpretationsArray = (Map<AnyPartialSymbol, AnyPartialInterpretation>[]) new Map[concretenessLength]; | ||
48 | partialInterpretations = interpretationsArray; | ||
49 | createPartialInterpretations(); | ||
50 | |||
51 | var refinerFactories = storeAdapter.getSymbolRefiners(); | ||
52 | refiners = new HashMap<>(refinerFactories.size()); | ||
53 | createRefiners(); | ||
54 | |||
55 | storageRefiners = storeAdapter.createStorageRefiner(model); | ||
56 | |||
57 | nodeCountInterpretation = model.getInterpretation(NODE_COUNT_SYMBOL); | ||
58 | if (model.getStore().getSymbols().contains(MultiObjectTranslator.COUNT_STORAGE)) { | ||
59 | countInterpretation = model.getInterpretation(MultiObjectTranslator.COUNT_STORAGE); | ||
60 | } else { | ||
61 | countInterpretation = null; | ||
62 | } | ||
63 | } | ||
64 | |||
65 | private void createPartialInterpretations() { | ||
66 | var supportedInterpretations = storeAdapter.getSupportedInterpretations(); | ||
67 | int concretenessLength = Concreteness.values().length; | ||
68 | var interpretationFactories = storeAdapter.getSymbolInterpreters(); | ||
69 | for (int i = 0; i < concretenessLength; i++) { | ||
70 | var concreteness = Concreteness.values()[i]; | ||
71 | if (supportedInterpretations.contains(concreteness)) { | ||
72 | partialInterpretations[i] = new HashMap<>(interpretationFactories.size()); | ||
73 | } | ||
74 | } | ||
75 | // Create the partial interpretations in order so that factories may refer to interpretations of symbols | ||
76 | // preceding them in the ordered {@code interpretationFactories} map, e.g., for opposite interpretations. | ||
77 | for (var entry : interpretationFactories.entrySet()) { | ||
78 | var partialSymbol = entry.getKey(); | ||
79 | var factory = entry.getValue(); | ||
80 | for (int i = 0; i < concretenessLength; i++) { | ||
81 | if (partialInterpretations[i] != null) { | ||
82 | var concreteness = Concreteness.values()[i]; | ||
83 | var interpretation = createPartialInterpretation(concreteness, factory, partialSymbol); | ||
84 | partialInterpretations[i].put(partialSymbol, interpretation); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | private <A, C> PartialInterpretation<A, C> createPartialInterpretation( | ||
91 | Concreteness concreteness, PartialInterpretation.Factory<A, C> interpreter, AnyPartialSymbol symbol) { | ||
92 | // The builder only allows well-typed assignment of interpreters to symbols. | ||
93 | @SuppressWarnings("unchecked") | ||
94 | var typedSymbol = (PartialSymbol<A, C>) symbol; | ||
95 | return interpreter.create(this, concreteness, typedSymbol); | ||
96 | } | ||
97 | |||
98 | private void createRefiners() { | ||
99 | var refinerFactories = storeAdapter.getSymbolRefiners(); | ||
100 | // Create the partial interpretations refiners in order so that factories may refer to refiners of symbols | ||
101 | // preceding them in the ordered {@code interpretationFactories} map, e.g., for opposite interpretations. | ||
102 | for (var entry : refinerFactories.entrySet()) { | ||
103 | var partialSymbol = entry.getKey(); | ||
104 | var factory = entry.getValue(); | ||
105 | var refiner = createRefiner(factory, partialSymbol); | ||
106 | refiners.put(partialSymbol, refiner); | ||
107 | } | ||
108 | } | ||
109 | |||
110 | private <A, C> PartialInterpretationRefiner<A, C> createRefiner( | ||
111 | PartialInterpretationRefiner.Factory<A, C> factory, AnyPartialSymbol symbol) { | ||
112 | // The builder only allows well-typed assignment of interpreters to symbols. | ||
113 | @SuppressWarnings("unchecked") | ||
114 | var typedSymbol = (PartialSymbol<A, C>) symbol; | ||
115 | return factory.create(this, typedSymbol); | ||
22 | } | 116 | } |
23 | 117 | ||
24 | @Override | 118 | @Override |
@@ -32,12 +126,88 @@ public class ReasoningAdapterImpl implements ReasoningAdapter { | |||
32 | } | 126 | } |
33 | 127 | ||
34 | @Override | 128 | @Override |
35 | public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) { | 129 | public <A, C> PartialInterpretation<A, C> getPartialInterpretation(Concreteness concreteness, |
36 | return null; | 130 | PartialSymbol<A, C> partialSymbol) { |
131 | var map = partialInterpretations[concreteness.ordinal()]; | ||
132 | if (map == null) { | ||
133 | throw new IllegalArgumentException("No interpretation for concreteness: " + concreteness); | ||
134 | } | ||
135 | var interpretation = map.get(partialSymbol); | ||
136 | if (interpretation == null) { | ||
137 | throw new IllegalArgumentException("No interpretation for partial symbol: " + partialSymbol); | ||
138 | } | ||
139 | // The builder only allows well-typed assignment of interpreters to symbols. | ||
140 | @SuppressWarnings("unchecked") | ||
141 | var typedInterpretation = (PartialInterpretation<A, C>) interpretation; | ||
142 | return typedInterpretation; | ||
143 | } | ||
144 | |||
145 | @Override | ||
146 | public <A, C> PartialInterpretationRefiner<A, C> getRefiner(PartialSymbol<A, C> partialSymbol) { | ||
147 | var refiner = refiners.get(partialSymbol); | ||
148 | if (refiner == null) { | ||
149 | throw new IllegalArgumentException("No refiner for partial symbol: " + partialSymbol); | ||
150 | } | ||
151 | // The builder only allows well-typed assignment of refiners to symbols. | ||
152 | @SuppressWarnings("unchecked") | ||
153 | var typedRefiner = (PartialInterpretationRefiner<A, C>) refiner; | ||
154 | return typedRefiner; | ||
155 | } | ||
156 | |||
157 | @Override | ||
158 | @Nullable | ||
159 | public Tuple1 split(int parentNode) { | ||
160 | int newNodeId = nodeCountInterpretation.get(Tuple.of()); | ||
161 | nodeCountInterpretation.put(Tuple.of(), newNodeId + 1); | ||
162 | // Avoid creating an iterator object. | ||
163 | //noinspection ForLoopReplaceableByForEach | ||
164 | for (int i = 0; i < storageRefiners.length; i++) { | ||
165 | if (!storageRefiners[i].split(parentNode, newNodeId)) { | ||
166 | return null; | ||
167 | } | ||
168 | } | ||
169 | return Tuple.of(newNodeId); | ||
170 | } | ||
171 | |||
172 | @Override | ||
173 | public @Nullable Tuple1 focus(int parentObject) { | ||
174 | if (countInterpretation == null) { | ||
175 | throw new IllegalStateException("Cannot focus without " + MultiObjectTranslator.class.getSimpleName()); | ||
176 | } | ||
177 | var tuple = Tuple.of(parentObject); | ||
178 | var count = countInterpretation.get(tuple); | ||
179 | if (CardinalityIntervals.ONE.equals(count)) { | ||
180 | return tuple; | ||
181 | } | ||
182 | if (CardinalityIntervals.LONE.equals(count)) { | ||
183 | countInterpretation.put(tuple, CardinalityIntervals.ONE); | ||
184 | return tuple; | ||
185 | } | ||
186 | if (CardinalityIntervals.NONE.equals(count)) { | ||
187 | return null; | ||
188 | } | ||
189 | return split(parentObject); | ||
190 | } | ||
191 | |||
192 | @Override | ||
193 | public boolean cleanup(int nodeToDelete) { | ||
194 | // Avoid creating an iterator object. | ||
195 | //noinspection ForLoopReplaceableByForEach | ||
196 | for (int i = 0; i < storageRefiners.length; i++) { | ||
197 | if (!storageRefiners[i].cleanup(nodeToDelete)) { | ||
198 | return false; | ||
199 | } | ||
200 | } | ||
201 | int currentModelSize = nodeCountInterpretation.get(Tuple.of()); | ||
202 | if (nodeToDelete == currentModelSize - 1) { | ||
203 | nodeCountInterpretation.put(Tuple.of(), nodeToDelete); | ||
204 | } | ||
205 | return true; | ||
37 | } | 206 | } |
38 | 207 | ||
39 | @Override | 208 | @Override |
40 | public ResultSet<Boolean> getLiftedResultSet(Dnf query) { | 209 | public int getNodeCount() { |
41 | return null; | 210 | Integer nodeCount = nodeCountInterpretation.get(Tuple.of()); |
211 | return nodeCount == null ? 0 : nodeCount; | ||
42 | } | 212 | } |
43 | } | 213 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java index aa71496c..722458c8 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java | |||
@@ -6,26 +6,169 @@ | |||
6 | package tools.refinery.store.reasoning.internal; | 6 | package tools.refinery.store.reasoning.internal; |
7 | 7 | ||
8 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; | 8 | import tools.refinery.store.adapter.AbstractModelAdapterBuilder; |
9 | import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder; | ||
10 | import tools.refinery.store.dse.transition.objectives.Objective; | ||
11 | import tools.refinery.store.dse.transition.objectives.Objectives; | ||
9 | import tools.refinery.store.model.ModelStore; | 12 | import tools.refinery.store.model.ModelStore; |
13 | import tools.refinery.store.model.ModelStoreBuilder; | ||
14 | import tools.refinery.store.query.ModelQueryBuilder; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | 15 | import tools.refinery.store.query.dnf.Dnf; |
16 | import tools.refinery.store.query.dnf.FunctionalQuery; | ||
17 | import tools.refinery.store.query.dnf.Query; | ||
18 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
11 | import tools.refinery.store.reasoning.ReasoningBuilder; | 19 | import tools.refinery.store.reasoning.ReasoningBuilder; |
20 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
21 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
22 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
12 | import tools.refinery.store.reasoning.literal.Modality; | 23 | import tools.refinery.store.reasoning.literal.Modality; |
24 | import tools.refinery.store.reasoning.refinement.DefaultStorageRefiner; | ||
25 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
26 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
27 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
28 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
29 | import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator; | ||
30 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
31 | import tools.refinery.store.representation.AnySymbol; | ||
32 | import tools.refinery.store.representation.Symbol; | ||
33 | import tools.refinery.store.statecoding.StateCoderBuilder; | ||
34 | |||
35 | import java.util.*; | ||
13 | 36 | ||
14 | public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl> | 37 | public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl> |
15 | implements ReasoningBuilder { | 38 | implements ReasoningBuilder { |
39 | private final DnfLifter lifter = new DnfLifter(); | ||
40 | private final PartialQueryRewriter queryRewriter = new PartialQueryRewriter(lifter); | ||
41 | private Set<Concreteness> requiredInterpretations = Set.of(Concreteness.values()); | ||
42 | private final Map<AnyPartialSymbol, AnyPartialSymbolTranslator> translators = new LinkedHashMap<>(); | ||
43 | private final Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters = | ||
44 | new LinkedHashMap<>(); | ||
45 | private final Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners = | ||
46 | new LinkedHashMap<>(); | ||
47 | private final Map<AnySymbol, StorageRefiner.Factory<?>> registeredStorageRefiners = new LinkedHashMap<>(); | ||
48 | private final List<PartialModelInitializer> initializers = new ArrayList<>(); | ||
49 | private final List<Objective> objectives = new ArrayList<>(); | ||
50 | |||
51 | @Override | ||
52 | public ReasoningBuilder requiredInterpretations(Collection<Concreteness> requiredInterpretations) { | ||
53 | this.requiredInterpretations = Set.copyOf(requiredInterpretations); | ||
54 | return this; | ||
55 | } | ||
56 | |||
57 | @Override | ||
58 | public ReasoningBuilder partialSymbol(AnyPartialSymbolTranslator translator) { | ||
59 | var partialSymbol = translator.getPartialSymbol(); | ||
60 | var oldConfiguration = translators.put(partialSymbol, translator); | ||
61 | if (oldConfiguration != null && oldConfiguration != translator) { | ||
62 | throw new IllegalArgumentException("Duplicate configuration for symbol: " + partialSymbol); | ||
63 | } | ||
64 | return this; | ||
65 | } | ||
66 | |||
16 | @Override | 67 | @Override |
17 | public ReasoningBuilder liftedQuery(Dnf liftedQuery) { | 68 | public <T> ReasoningBuilder storageRefiner(Symbol<T> symbol, StorageRefiner.Factory<T> refiner) { |
18 | return null; | 69 | checkNotConfigured(); |
70 | if (registeredStorageRefiners.put(symbol, refiner) != null) { | ||
71 | throw new IllegalArgumentException("Duplicate representation refiner for symbol: " + symbol); | ||
72 | } | ||
73 | return this; | ||
74 | } | ||
75 | |||
76 | @Override | ||
77 | public ReasoningBuilder initializer(PartialModelInitializer initializer) { | ||
78 | checkNotConfigured(); | ||
79 | initializers.add(initializer); | ||
80 | return this; | ||
19 | } | 81 | } |
20 | 82 | ||
21 | @Override | 83 | @Override |
22 | public Dnf lift(Modality modality, Dnf query) { | 84 | public ReasoningBuilder objective(Objective objective) { |
23 | checkNotConfigured(); | 85 | checkNotConfigured(); |
24 | return null; | 86 | objectives.add(objective); |
87 | return this; | ||
88 | } | ||
89 | |||
90 | @Override | ||
91 | public <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query) { | ||
92 | return lifter.lift(modality, concreteness, query); | ||
93 | } | ||
94 | |||
95 | @Override | ||
96 | public RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query) { | ||
97 | return lifter.lift(modality, concreteness, query); | ||
98 | } | ||
99 | |||
100 | @Override | ||
101 | public <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query) { | ||
102 | return lifter.lift(modality, concreteness, query); | ||
103 | } | ||
104 | |||
105 | @Override | ||
106 | public Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf) { | ||
107 | return lifter.lift(modality, concreteness, dnf); | ||
108 | } | ||
109 | |||
110 | @Override | ||
111 | protected void doConfigure(ModelStoreBuilder storeBuilder) { | ||
112 | storeBuilder.symbols(ReasoningAdapterImpl.NODE_COUNT_SYMBOL); | ||
113 | storeBuilder.tryGetAdapter(StateCoderBuilder.class) | ||
114 | .ifPresent(stateCoderBuilder -> stateCoderBuilder.exclude(ReasoningAdapterImpl.NODE_COUNT_SYMBOL)); | ||
115 | for (var translator : translators.values()) { | ||
116 | translator.configure(storeBuilder); | ||
117 | if (translator instanceof PartialRelationTranslator relationConfiguration) { | ||
118 | doConfigure(storeBuilder, relationConfiguration); | ||
119 | } else { | ||
120 | throw new IllegalArgumentException("Unknown partial symbol translator %s for partial symbol %s" | ||
121 | .formatted(translator, translator.getPartialSymbol())); | ||
122 | } | ||
123 | } | ||
124 | storeBuilder.symbols(registeredStorageRefiners.keySet()); | ||
125 | var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class); | ||
126 | queryBuilder.rewriter(queryRewriter); | ||
127 | if (!objectives.isEmpty()) { | ||
128 | storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class) | ||
129 | .ifPresent(dseBuilder -> dseBuilder.objective(Objectives.sum(objectives))); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | private void doConfigure(ModelStoreBuilder storeBuilder, PartialRelationTranslator relationConfiguration) { | ||
134 | var partialRelation = relationConfiguration.getPartialRelation(); | ||
135 | queryRewriter.addRelationRewriter(partialRelation, relationConfiguration.getRewriter()); | ||
136 | var interpretationFactory = relationConfiguration.getInterpretationFactory(); | ||
137 | interpretationFactory.configure(storeBuilder, requiredInterpretations); | ||
138 | symbolInterpreters.put(partialRelation, interpretationFactory); | ||
139 | var refiner = relationConfiguration.getInterpretationRefiner(); | ||
140 | if (refiner != null) { | ||
141 | symbolRefiners.put(partialRelation, refiner); | ||
142 | } | ||
25 | } | 143 | } |
26 | 144 | ||
27 | @Override | 145 | @Override |
28 | public ReasoningStoreAdapterImpl doBuild(ModelStore store) { | 146 | public ReasoningStoreAdapterImpl doBuild(ModelStore store) { |
29 | return null; | 147 | return new ReasoningStoreAdapterImpl(store, requiredInterpretations, |
148 | Collections.unmodifiableMap(symbolInterpreters), Collections.unmodifiableMap(symbolRefiners), | ||
149 | getStorageRefiners(store), Collections.unmodifiableList(initializers)); | ||
150 | } | ||
151 | |||
152 | private Map<AnySymbol, StorageRefiner.Factory<?>> getStorageRefiners(ModelStore store) { | ||
153 | var symbols = store.getSymbols(); | ||
154 | var storageRefiners = new LinkedHashMap<AnySymbol, StorageRefiner.Factory<?>>(symbols.size()); | ||
155 | for (var symbol : symbols) { | ||
156 | var refiner = registeredStorageRefiners.remove(symbol); | ||
157 | if (refiner == null) { | ||
158 | if (symbol.arity() == 0) { | ||
159 | // Arity-0 symbols don't need a default refiner, because they are unaffected by object | ||
160 | // creation/removal. Only a custom refiner ({@code refiner != null}) might need to update them. | ||
161 | continue; | ||
162 | } | ||
163 | // By default, copy over all affected tuples on object creation and remove all affected tuples on | ||
164 | // object removal. | ||
165 | refiner = DefaultStorageRefiner.factory(); | ||
166 | } | ||
167 | storageRefiners.put(symbol, refiner); | ||
168 | } | ||
169 | if (!registeredStorageRefiners.isEmpty()) { | ||
170 | throw new IllegalArgumentException("Unused storage refiners: " + registeredStorageRefiners.keySet()); | ||
171 | } | ||
172 | return Collections.unmodifiableMap(storageRefiners); | ||
30 | } | 173 | } |
31 | } | 174 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java index cdddd8d6..9ef6fb16 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java | |||
@@ -5,19 +5,46 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.internal; | 6 | package tools.refinery.store.reasoning.internal; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | 8 | import tools.refinery.store.dse.propagation.PropagationAdapter; |
9 | import tools.refinery.store.model.Model; | 9 | import tools.refinery.store.model.Model; |
10 | import tools.refinery.store.model.ModelStore; | 10 | import tools.refinery.store.model.ModelStore; |
11 | import tools.refinery.store.query.ModelQueryAdapter; | ||
12 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
13 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
16 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
17 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | 18 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; |
12 | import tools.refinery.store.query.dnf.Dnf; | 19 | import tools.refinery.store.reasoning.seed.ModelSeed; |
20 | import tools.refinery.store.representation.AnySymbol; | ||
21 | import tools.refinery.store.representation.Symbol; | ||
22 | import tools.refinery.store.tuple.Tuple; | ||
13 | 23 | ||
14 | import java.util.Collection; | 24 | import java.util.Collection; |
25 | import java.util.List; | ||
26 | import java.util.Map; | ||
27 | import java.util.Set; | ||
15 | 28 | ||
16 | public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { | 29 | class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { |
17 | private final ModelStore store; | 30 | private final ModelStore store; |
31 | private final Set<Concreteness> supportedInterpretations; | ||
32 | private final Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters; | ||
33 | private final Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners; | ||
34 | private final Map<AnySymbol, StorageRefiner.Factory<?>> storageRefiners; | ||
35 | private final List<PartialModelInitializer> initializers; | ||
18 | 36 | ||
19 | ReasoningStoreAdapterImpl(ModelStore store) { | 37 | ReasoningStoreAdapterImpl(ModelStore store, Set<Concreteness> supportedInterpretations, |
38 | Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> symbolInterpreters, | ||
39 | Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> symbolRefiners, | ||
40 | Map<AnySymbol, StorageRefiner.Factory<?>> storageRefiners, | ||
41 | List<PartialModelInitializer> initializers) { | ||
20 | this.store = store; | 42 | this.store = store; |
43 | this.supportedInterpretations = supportedInterpretations; | ||
44 | this.symbolInterpreters = symbolInterpreters; | ||
45 | this.symbolRefiners = symbolRefiners; | ||
46 | this.storageRefiners = storageRefiners; | ||
47 | this.initializers = initializers; | ||
21 | } | 48 | } |
22 | 49 | ||
23 | @Override | 50 | @Override |
@@ -26,13 +53,64 @@ public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { | |||
26 | } | 53 | } |
27 | 54 | ||
28 | @Override | 55 | @Override |
56 | public Set<Concreteness> getSupportedInterpretations() { | ||
57 | return supportedInterpretations; | ||
58 | } | ||
59 | |||
60 | @Override | ||
29 | public Collection<AnyPartialSymbol> getPartialSymbols() { | 61 | public Collection<AnyPartialSymbol> getPartialSymbols() { |
30 | return null; | 62 | return symbolInterpreters.keySet(); |
31 | } | 63 | } |
32 | 64 | ||
33 | @Override | 65 | @Override |
34 | public Collection<Dnf> getLiftedQueries() { | 66 | public Collection<AnyPartialSymbol> getRefinablePartialSymbols() { |
35 | return null; | 67 | return symbolRefiners.keySet(); |
68 | } | ||
69 | |||
70 | // Use of wildcard return value only in internal method not exposed as API, so there is less chance of confusion. | ||
71 | @SuppressWarnings("squid:S1452") | ||
72 | Map<AnyPartialSymbol, PartialInterpretation.Factory<?, ?>> getSymbolInterpreters() { | ||
73 | return symbolInterpreters; | ||
74 | } | ||
75 | |||
76 | // Use of wildcard return value only in internal method not exposed as API, so there is less chance of confusion. | ||
77 | @SuppressWarnings("squid:S1452") | ||
78 | Map<AnyPartialSymbol, PartialInterpretationRefiner.Factory<?, ?>> getSymbolRefiners() { | ||
79 | return symbolRefiners; | ||
80 | } | ||
81 | |||
82 | StorageRefiner[] createStorageRefiner(Model model) { | ||
83 | var refiners = new StorageRefiner[storageRefiners.size()]; | ||
84 | int i = 0; | ||
85 | for (var entry : storageRefiners.entrySet()) { | ||
86 | var symbol = entry.getKey(); | ||
87 | var factory = entry.getValue(); | ||
88 | refiners[i] = createStorageRefiner(factory, model, symbol); | ||
89 | i++; | ||
90 | } | ||
91 | return refiners; | ||
92 | } | ||
93 | |||
94 | private <T> StorageRefiner createStorageRefiner(StorageRefiner.Factory<T> factory, Model model, AnySymbol symbol) { | ||
95 | // The builder only allows well-typed assignment of refiners to symbols. | ||
96 | @SuppressWarnings("unchecked") | ||
97 | var typedSymbol = (Symbol<T>) symbol; | ||
98 | return factory.create(typedSymbol, model); | ||
99 | } | ||
100 | |||
101 | public Model createInitialModel(ModelSeed modelSeed) { | ||
102 | var model = store.createEmptyModel(); | ||
103 | model.getInterpretation(ReasoningAdapterImpl.NODE_COUNT_SYMBOL).put(Tuple.of(), modelSeed.getNodeCount()); | ||
104 | for (var initializer : initializers) { | ||
105 | initializer.initialize(model, modelSeed); | ||
106 | } | ||
107 | model.tryGetAdapter(PropagationAdapter.class).ifPresent(propagationAdapter -> { | ||
108 | if (propagationAdapter.propagate().isRejected()) { | ||
109 | throw new IllegalArgumentException("Inconsistent initial mode: propagation failed"); | ||
110 | } | ||
111 | }); | ||
112 | model.getAdapter(ModelQueryAdapter.class).flushChanges(); | ||
113 | return model; | ||
36 | } | 114 | } |
37 | 115 | ||
38 | @Override | 116 | @Override |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java new file mode 100644 index 00000000..ed291eac --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.interpretation; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | |||
12 | public abstract class AbstractPartialInterpretation<A, C> implements PartialInterpretation<A, C> { | ||
13 | private final ReasoningAdapter adapter; | ||
14 | private final PartialSymbol<A, C> partialSymbol; | ||
15 | private final Concreteness concreteness; | ||
16 | |||
17 | protected AbstractPartialInterpretation(ReasoningAdapter adapter, Concreteness concreteness, | ||
18 | PartialSymbol<A, C> partialSymbol) { | ||
19 | this.adapter = adapter; | ||
20 | this.partialSymbol = partialSymbol; | ||
21 | this.concreteness = concreteness; | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public ReasoningAdapter getAdapter() { | ||
26 | return adapter; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public PartialSymbol<A, C> getPartialSymbol() { | ||
31 | return partialSymbol; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Concreteness getConcreteness() { | ||
36 | return concreteness; | ||
37 | } | ||
38 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AnyPartialInterpretation.java index 000171a1..cd709bc4 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AnyPartialInterpretation.java | |||
@@ -3,8 +3,10 @@ | |||
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning; | 6 | package tools.refinery.store.reasoning.interpretation; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
8 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | 10 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; |
9 | 11 | ||
10 | public sealed interface AnyPartialInterpretation permits PartialInterpretation { | 12 | public sealed interface AnyPartialInterpretation permits PartialInterpretation { |
@@ -12,7 +14,5 @@ public sealed interface AnyPartialInterpretation permits PartialInterpretation { | |||
12 | 14 | ||
13 | AnyPartialSymbol getPartialSymbol(); | 15 | AnyPartialSymbol getPartialSymbol(); |
14 | 16 | ||
15 | int countUnfinished(); | 17 | Concreteness getConcreteness(); |
16 | |||
17 | int countErrors(); | ||
18 | } | 18 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java new file mode 100644 index 00000000..86ffe751 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.interpretation; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.model.ModelStoreBuilder; | ||
10 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
11 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
12 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
13 | import tools.refinery.store.tuple.Tuple; | ||
14 | |||
15 | import java.util.Set; | ||
16 | |||
17 | public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation { | ||
18 | @Override | ||
19 | PartialSymbol<A, C> getPartialSymbol(); | ||
20 | |||
21 | A get(Tuple key); | ||
22 | |||
23 | Cursor<Tuple, A> getAll(); | ||
24 | |||
25 | @FunctionalInterface | ||
26 | interface Factory<A, C> { | ||
27 | PartialInterpretation<A, C> create(ReasoningAdapter adapter, Concreteness concreteness, | ||
28 | PartialSymbol<A, C> partialSymbol); | ||
29 | |||
30 | default void configure(ModelStoreBuilder storeBuilder, Set<Concreteness> requiredInterpretations) { | ||
31 | // Nothing to configure by default. | ||
32 | } | ||
33 | } | ||
34 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java new file mode 100644 index 00000000..6ad35c20 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.interpretation; | ||
7 | |||
8 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
9 | import tools.refinery.store.query.literal.Literal; | ||
10 | import tools.refinery.store.query.term.Variable; | ||
11 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
12 | import tools.refinery.store.reasoning.literal.Modality; | ||
13 | |||
14 | import java.util.List; | ||
15 | import java.util.Set; | ||
16 | |||
17 | @FunctionalInterface | ||
18 | public interface PartialRelationRewriter { | ||
19 | List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, Modality modality, | ||
20 | Concreteness concreteness); | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java new file mode 100644 index 00000000..5cdaa185 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java | |||
@@ -0,0 +1,195 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.interpretation; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.model.ModelStoreBuilder; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.ModelQueryBuilder; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.resultset.ResultSet; | ||
14 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
15 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
16 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
17 | import tools.refinery.store.representation.TruthValue; | ||
18 | import tools.refinery.store.tuple.Tuple; | ||
19 | |||
20 | import java.util.Set; | ||
21 | |||
22 | public class QueryBasedRelationInterpretationFactory implements PartialInterpretation.Factory<TruthValue, Boolean> { | ||
23 | private final Query<Boolean> may; | ||
24 | private final Query<Boolean> must; | ||
25 | private final Query<Boolean> candidateMay; | ||
26 | private final Query<Boolean> candidateMust; | ||
27 | |||
28 | public QueryBasedRelationInterpretationFactory( | ||
29 | Query<Boolean> may, Query<Boolean> must, Query<Boolean> candidateMay, Query<Boolean> candidateMust) { | ||
30 | this.may = may; | ||
31 | this.must = must; | ||
32 | this.candidateMay = candidateMay; | ||
33 | this.candidateMust = candidateMust; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public PartialInterpretation<TruthValue, Boolean> create( | ||
38 | ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol) { | ||
39 | var queryEngine = adapter.getModel().getAdapter(ModelQueryAdapter.class); | ||
40 | ResultSet<Boolean> mayResultSet; | ||
41 | ResultSet<Boolean> mustResultSet; | ||
42 | switch (concreteness) { | ||
43 | case PARTIAL -> { | ||
44 | mayResultSet = queryEngine.getResultSet(may); | ||
45 | mustResultSet = queryEngine.getResultSet(must); | ||
46 | } | ||
47 | case CANDIDATE -> { | ||
48 | mayResultSet = queryEngine.getResultSet(candidateMay); | ||
49 | mustResultSet = queryEngine.getResultSet(candidateMust); | ||
50 | } | ||
51 | default -> throw new IllegalArgumentException("Unknown concreteness: " + concreteness); | ||
52 | } | ||
53 | if (mayResultSet.equals(mustResultSet)) { | ||
54 | return new TwoValuedInterpretation(adapter, concreteness, partialSymbol, mustResultSet); | ||
55 | } else { | ||
56 | return new FourValuedInterpretation( | ||
57 | adapter, concreteness, partialSymbol, mayResultSet, mustResultSet); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | @Override | ||
62 | public void configure(ModelStoreBuilder storeBuilder, Set<Concreteness> requiredInterpretations) { | ||
63 | var queryBuilder = storeBuilder.getAdapter(ModelQueryBuilder.class); | ||
64 | if (requiredInterpretations.contains(Concreteness.PARTIAL)) { | ||
65 | queryBuilder.queries(may, must); | ||
66 | } | ||
67 | if (requiredInterpretations.contains(Concreteness.CANDIDATE)) { | ||
68 | queryBuilder.queries(candidateMay, candidateMust); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | private static class TwoValuedInterpretation extends AbstractPartialInterpretation<TruthValue, Boolean> { | ||
73 | private final ResultSet<Boolean> resultSet; | ||
74 | |||
75 | protected TwoValuedInterpretation( | ||
76 | ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
77 | ResultSet<Boolean> resultSet) { | ||
78 | super(adapter, concreteness, partialSymbol); | ||
79 | this.resultSet = resultSet; | ||
80 | } | ||
81 | |||
82 | @Override | ||
83 | public TruthValue get(Tuple key) { | ||
84 | return TruthValue.toTruthValue(resultSet.get(key)); | ||
85 | } | ||
86 | |||
87 | @Override | ||
88 | public Cursor<Tuple, TruthValue> getAll() { | ||
89 | return new TwoValuedCursor(resultSet.getAll()); | ||
90 | } | ||
91 | |||
92 | private record TwoValuedCursor(Cursor<Tuple, Boolean> cursor) implements Cursor<Tuple, TruthValue> { | ||
93 | @Override | ||
94 | public Tuple getKey() { | ||
95 | return cursor.getKey(); | ||
96 | } | ||
97 | |||
98 | @Override | ||
99 | public TruthValue getValue() { | ||
100 | return TruthValue.toTruthValue(cursor.getValue()); | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public boolean isTerminated() { | ||
105 | return cursor.isTerminated(); | ||
106 | } | ||
107 | |||
108 | @Override | ||
109 | public boolean move() { | ||
110 | return cursor.move(); | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | private static class FourValuedInterpretation extends AbstractPartialInterpretation<TruthValue, Boolean> { | ||
116 | private final ResultSet<Boolean> mayResultSet; | ||
117 | private final ResultSet<Boolean> mustResultSet; | ||
118 | |||
119 | public FourValuedInterpretation( | ||
120 | ReasoningAdapter adapter, Concreteness concreteness, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
121 | ResultSet<Boolean> mayResultSet, ResultSet<Boolean> mustResultSet) { | ||
122 | super(adapter, concreteness, partialSymbol); | ||
123 | this.mayResultSet = mayResultSet; | ||
124 | this.mustResultSet = mustResultSet; | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public TruthValue get(Tuple key) { | ||
129 | boolean isMay = mayResultSet.get(key); | ||
130 | boolean isMust = mustResultSet.get(key); | ||
131 | if (isMust) { | ||
132 | return isMay ? TruthValue.TRUE : TruthValue.ERROR; | ||
133 | } else { | ||
134 | return isMay ? TruthValue.UNKNOWN : TruthValue.FALSE; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | @Override | ||
139 | public Cursor<Tuple, TruthValue> getAll() { | ||
140 | return new FourValuedCursor(); | ||
141 | } | ||
142 | |||
143 | private final class FourValuedCursor implements Cursor<Tuple, TruthValue> { | ||
144 | private final Cursor<Tuple, Boolean> mayCursor; | ||
145 | private Cursor<Tuple, Boolean> mustCursor; | ||
146 | |||
147 | private FourValuedCursor() { | ||
148 | this.mayCursor = mayResultSet.getAll(); | ||
149 | } | ||
150 | |||
151 | @Override | ||
152 | public Tuple getKey() { | ||
153 | return mustCursor == null ? mayCursor.getKey() : mustCursor.getKey(); | ||
154 | } | ||
155 | |||
156 | @Override | ||
157 | public TruthValue getValue() { | ||
158 | if (mustCursor != null) { | ||
159 | return TruthValue.ERROR; | ||
160 | } | ||
161 | if (Boolean.TRUE.equals(mustResultSet.get(mayCursor.getKey()))) { | ||
162 | return TruthValue.TRUE; | ||
163 | } | ||
164 | return TruthValue.UNKNOWN; | ||
165 | } | ||
166 | |||
167 | @Override | ||
168 | public boolean isTerminated() { | ||
169 | return mustCursor != null && mustCursor.isTerminated(); | ||
170 | } | ||
171 | |||
172 | @Override | ||
173 | public boolean move() { | ||
174 | if (mayCursor.isTerminated()) { | ||
175 | return moveMust(); | ||
176 | } | ||
177 | if (mayCursor.move()) { | ||
178 | return true; | ||
179 | } | ||
180 | mustCursor = mustResultSet.getAll(); | ||
181 | return moveMust(); | ||
182 | } | ||
183 | |||
184 | private boolean moveMust() { | ||
185 | while (mustCursor.move()) { | ||
186 | // We already iterated over {@code TRUE} truth values with {@code mayCursor}. | ||
187 | if (!Boolean.TRUE.equals(mayResultSet.get(mustCursor.getKey()))) { | ||
188 | return true; | ||
189 | } | ||
190 | } | ||
191 | return false; | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java new file mode 100644 index 00000000..78fdbb89 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java | |||
@@ -0,0 +1,63 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.interpretation; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
9 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
13 | import tools.refinery.store.reasoning.literal.Modality; | ||
14 | |||
15 | import java.util.List; | ||
16 | import java.util.Set; | ||
17 | |||
18 | public class QueryBasedRelationRewriter implements PartialRelationRewriter { | ||
19 | private final RelationalQuery may; | ||
20 | private final RelationalQuery must; | ||
21 | private final RelationalQuery candidateMay; | ||
22 | private final RelationalQuery candidateMust; | ||
23 | |||
24 | public QueryBasedRelationRewriter(RelationalQuery may, RelationalQuery must, RelationalQuery candidateMay, | ||
25 | RelationalQuery candidateMust) { | ||
26 | this.may = may; | ||
27 | this.must = must; | ||
28 | this.candidateMay = candidateMay; | ||
29 | this.candidateMust = candidateMust; | ||
30 | } | ||
31 | |||
32 | public RelationalQuery getMay() { | ||
33 | return may; | ||
34 | } | ||
35 | |||
36 | public RelationalQuery getMust() { | ||
37 | return must; | ||
38 | } | ||
39 | |||
40 | public RelationalQuery getCandidateMay() { | ||
41 | return candidateMay; | ||
42 | } | ||
43 | |||
44 | public RelationalQuery getCandidateMust() { | ||
45 | return candidateMust; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, | ||
50 | Modality modality, Concreteness concreteness) { | ||
51 | var query = switch (concreteness) { | ||
52 | case PARTIAL -> switch (modality) { | ||
53 | case MAY -> may; | ||
54 | case MUST -> must; | ||
55 | }; | ||
56 | case CANDIDATE -> switch (modality) { | ||
57 | case MAY -> candidateMay; | ||
58 | case MUST -> candidateMust; | ||
59 | }; | ||
60 | }; | ||
61 | return List.of(literal.withTarget(query.getDnf())); | ||
62 | } | ||
63 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java new file mode 100644 index 00000000..17916c02 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java | |||
@@ -0,0 +1,182 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.lifting; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.dnf.Dnf; | ||
10 | import tools.refinery.store.query.dnf.DnfClause; | ||
11 | import tools.refinery.store.query.literal.*; | ||
12 | import tools.refinery.store.query.term.NodeVariable; | ||
13 | import tools.refinery.store.query.term.ParameterDirection; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
16 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
17 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
18 | import tools.refinery.store.reasoning.literal.Modality; | ||
19 | |||
20 | import java.util.*; | ||
21 | import java.util.stream.Collectors; | ||
22 | |||
23 | class ClauseLifter { | ||
24 | private final Modality modality; | ||
25 | private final Concreteness concreteness; | ||
26 | private final DnfClause clause; | ||
27 | private final Set<NodeVariable> quantifiedVariables; | ||
28 | private final Set<NodeVariable> existentialQuantifiersToAdd; | ||
29 | |||
30 | public ClauseLifter(Modality modality, Concreteness concreteness, Dnf dnf, DnfClause clause) { | ||
31 | this.modality = modality; | ||
32 | this.concreteness = concreteness; | ||
33 | this.clause = clause; | ||
34 | quantifiedVariables = getQuantifiedNodeVariables(dnf, clause); | ||
35 | existentialQuantifiersToAdd = new LinkedHashSet<>(quantifiedVariables); | ||
36 | } | ||
37 | |||
38 | private static Set<NodeVariable> getQuantifiedNodeVariables(Dnf dnf, DnfClause clause) { | ||
39 | var quantifiedVariables = clause.positiveVariables().stream() | ||
40 | .filter(Variable::isNodeVariable) | ||
41 | .map(Variable::asNodeVariable) | ||
42 | .collect(Collectors.toCollection(LinkedHashSet::new)); | ||
43 | for (var symbolicParameter : dnf.getSymbolicParameters()) { | ||
44 | if (symbolicParameter.getVariable() instanceof NodeVariable nodeParameter) { | ||
45 | quantifiedVariables.remove(nodeParameter); | ||
46 | } | ||
47 | } | ||
48 | return Collections.unmodifiableSet(quantifiedVariables); | ||
49 | } | ||
50 | |||
51 | public List<Literal> liftClause() { | ||
52 | var liftedLiterals = new ArrayList<Literal>(); | ||
53 | for (var literal : clause.literals()) { | ||
54 | var liftedLiteral = liftLiteral(literal); | ||
55 | liftedLiterals.add(liftedLiteral); | ||
56 | } | ||
57 | var existsConstraint = ModalConstraint.of(modality, concreteness, ReasoningAdapter.EXISTS_SYMBOL); | ||
58 | for (var quantifiedVariable : existentialQuantifiersToAdd) { | ||
59 | liftedLiterals.add(existsConstraint.call(quantifiedVariable)); | ||
60 | } | ||
61 | return liftedLiterals; | ||
62 | } | ||
63 | |||
64 | private Literal liftLiteral(Literal literal) { | ||
65 | if (literal instanceof CallLiteral callLiteral) { | ||
66 | return liftCallLiteral(callLiteral); | ||
67 | } else if (literal instanceof EquivalenceLiteral equivalenceLiteral) { | ||
68 | return liftEquivalenceLiteral(equivalenceLiteral); | ||
69 | } else if (literal instanceof ConstantLiteral || | ||
70 | literal instanceof AssignLiteral<?> || | ||
71 | literal instanceof CheckLiteral) { | ||
72 | return literal; | ||
73 | } else if (literal instanceof AbstractCountLiteral<?>) { | ||
74 | throw new IllegalArgumentException("Count literal %s cannot be lifted".formatted(literal)); | ||
75 | } else if (literal instanceof AggregationLiteral<?, ?>) { | ||
76 | throw new IllegalArgumentException("Aggregation literal %s cannot be lifted".formatted(literal)); | ||
77 | } else if (literal instanceof RepresentativeElectionLiteral) { | ||
78 | throw new IllegalArgumentException("SCC literal %s cannot be lifted".formatted(literal)); | ||
79 | } else { | ||
80 | throw new IllegalArgumentException("Unknown literal to lift: " + literal); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | private Literal liftCallLiteral(CallLiteral callLiteral) { | ||
85 | var polarity = callLiteral.getPolarity(); | ||
86 | return switch (polarity) { | ||
87 | case POSITIVE -> { | ||
88 | Constraint target = callLiteral.getTarget(); | ||
89 | var arguments = callLiteral.getArguments(); | ||
90 | yield ModalConstraint.of(modality, concreteness, target).call(CallPolarity.POSITIVE, arguments); | ||
91 | } | ||
92 | case NEGATIVE -> callNegationHelper(callLiteral); | ||
93 | case TRANSITIVE -> callTransitiveHelper(callLiteral); | ||
94 | }; | ||
95 | } | ||
96 | |||
97 | private Literal callNegationHelper(CallLiteral callLiteral) { | ||
98 | var target = callLiteral.getTarget(); | ||
99 | var originalArguments = callLiteral.getArguments(); | ||
100 | var negatedModality = modality.negate(); | ||
101 | var privateVariables = callLiteral.getPrivateVariables(clause.positiveVariables()); | ||
102 | if (privateVariables.isEmpty()) { | ||
103 | // If there is no universal quantification, we may directly call the original Dnf. | ||
104 | return ModalConstraint.of(negatedModality, concreteness, target) | ||
105 | .call(CallPolarity.NEGATIVE, originalArguments); | ||
106 | } | ||
107 | |||
108 | var builder = Dnf.builder("%s#negation#%s#%s#%s" | ||
109 | .formatted(target.name(), modality, concreteness, privateVariables)); | ||
110 | var uniqueOriginalArguments = List.copyOf(new LinkedHashSet<>(originalArguments)); | ||
111 | |||
112 | var alwaysInputVariables = callLiteral.getInputVariables(Set.of()); | ||
113 | for (var variable : uniqueOriginalArguments) { | ||
114 | var direction = alwaysInputVariables.contains(variable) ? ParameterDirection.IN : ParameterDirection.OUT; | ||
115 | builder.parameter(variable, direction); | ||
116 | } | ||
117 | |||
118 | var literals = new ArrayList<Literal>(); | ||
119 | var liftedConstraint = ModalConstraint.of(negatedModality, concreteness, target); | ||
120 | literals.add(liftedConstraint.call(CallPolarity.POSITIVE, originalArguments)); | ||
121 | |||
122 | var existsConstraint = ModalConstraint.of(negatedModality, concreteness, ReasoningAdapter.EXISTS_SYMBOL); | ||
123 | for (var variable : uniqueOriginalArguments) { | ||
124 | if (privateVariables.contains(variable)) { | ||
125 | literals.add(existsConstraint.call(variable)); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | builder.clause(literals); | ||
130 | var liftedTarget = builder.build(); | ||
131 | return liftedTarget.call(CallPolarity.NEGATIVE, uniqueOriginalArguments); | ||
132 | } | ||
133 | |||
134 | private Literal callTransitiveHelper(CallLiteral callLiteral) { | ||
135 | var target = callLiteral.getTarget(); | ||
136 | var originalArguments = callLiteral.getArguments(); | ||
137 | var liftedTarget = ModalConstraint.of(modality, concreteness, target); | ||
138 | |||
139 | var existsConstraint = ModalConstraint.of(modality, concreteness, ReasoningAdapter.EXISTS_SYMBOL); | ||
140 | var existingEndHelperName = "%s#exisitingEnd#%s#%s".formatted(target.name(), modality, concreteness); | ||
141 | var existingEndHelper = Dnf.of(existingEndHelperName, builder -> { | ||
142 | var start = builder.parameter("start"); | ||
143 | var end = builder.parameter("end"); | ||
144 | builder.clause( | ||
145 | liftedTarget.call(start, end), | ||
146 | existsConstraint.call(end) | ||
147 | ); | ||
148 | }); | ||
149 | |||
150 | // The start and end of a transitive path is always a node. | ||
151 | var pathEnd = (NodeVariable) originalArguments.get(1); | ||
152 | if (quantifiedVariables.contains(pathEnd)) { | ||
153 | // The end of the path needs existential quantification anyway, so we don't need a second helper. | ||
154 | // We replace the call to EXISTS with the transitive path call. | ||
155 | existentialQuantifiersToAdd.remove(pathEnd); | ||
156 | return existingEndHelper.call(CallPolarity.TRANSITIVE, originalArguments); | ||
157 | } | ||
158 | |||
159 | var transitiveHelperName = "%s#transitive#%s#%s".formatted(target.name(), modality, concreteness); | ||
160 | var transitiveHelper = Dnf.of(transitiveHelperName, builder -> { | ||
161 | var start = builder.parameter("start"); | ||
162 | var end = builder.parameter("end"); | ||
163 | // Make sure the end of the path is not existentially quantified. | ||
164 | builder.clause(liftedTarget.call(start, end)); | ||
165 | builder.clause(middle -> List.of( | ||
166 | existingEndHelper.callTransitive(start, middle), | ||
167 | liftedTarget.call(middle, end) | ||
168 | )); | ||
169 | }); | ||
170 | |||
171 | return transitiveHelper.call(CallPolarity.POSITIVE, originalArguments); | ||
172 | } | ||
173 | |||
174 | private Literal liftEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral) { | ||
175 | if (equivalenceLiteral.isPositive()) { | ||
176 | return ModalConstraint.of(modality, concreteness, ReasoningAdapter.EQUALS_SYMBOL) | ||
177 | .call(CallPolarity.POSITIVE, equivalenceLiteral.getLeft(), equivalenceLiteral.getRight()); | ||
178 | } | ||
179 | return ModalConstraint.of(modality.negate(), concreteness, ReasoningAdapter.EQUALS_SYMBOL) | ||
180 | .call(CallPolarity.NEGATIVE, equivalenceLiteral.getLeft(), equivalenceLiteral.getRight()); | ||
181 | } | ||
182 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java index ac41d170..889f595f 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java | |||
@@ -5,124 +5,68 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.lifting; | 6 | package tools.refinery.store.reasoning.lifting; |
7 | 7 | ||
8 | import org.jetbrains.annotations.Nullable; | 8 | import tools.refinery.store.query.dnf.*; |
9 | import tools.refinery.store.query.dnf.Dnf; | 9 | import tools.refinery.store.query.equality.DnfEqualityChecker; |
10 | import tools.refinery.store.query.dnf.DnfBuilder; | ||
11 | import tools.refinery.store.query.dnf.DnfClause; | ||
12 | import tools.refinery.store.query.literal.CallLiteral; | ||
13 | import tools.refinery.store.query.literal.CallPolarity; | ||
14 | import tools.refinery.store.query.literal.Literal; | 10 | import tools.refinery.store.query.literal.Literal; |
15 | import tools.refinery.store.query.term.NodeVariable; | 11 | import tools.refinery.store.reasoning.literal.Concreteness; |
16 | import tools.refinery.store.query.term.Variable; | ||
17 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
18 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
19 | import tools.refinery.store.reasoning.literal.Modality; | 12 | import tools.refinery.store.reasoning.literal.Modality; |
20 | import tools.refinery.store.reasoning.literal.PartialLiterals; | ||
21 | import tools.refinery.store.util.CycleDetectingMapper; | ||
22 | 13 | ||
23 | import java.util.ArrayList; | 14 | import java.util.HashMap; |
24 | import java.util.LinkedHashSet; | ||
25 | import java.util.List; | 15 | import java.util.List; |
16 | import java.util.Map; | ||
26 | 17 | ||
27 | public class DnfLifter { | 18 | public class DnfLifter { |
28 | private final CycleDetectingMapper<ModalDnf, Dnf> mapper = new CycleDetectingMapper<>(ModalDnf::toString, | 19 | private final Map<ModalDnf, Dnf> cache = new HashMap<>(); |
29 | this::doLift); | ||
30 | 20 | ||
31 | public Dnf lift(Modality modality, Dnf query) { | 21 | public <T> Query<T> lift(Modality modality, Concreteness concreteness, Query<T> query) { |
32 | return mapper.map(new ModalDnf(modality, query)); | 22 | var liftedDnf = lift(modality, concreteness, query.getDnf()); |
23 | return query.withDnf(liftedDnf); | ||
24 | } | ||
25 | |||
26 | public RelationalQuery lift(Modality modality, Concreteness concreteness, RelationalQuery query) { | ||
27 | var liftedDnf = lift(modality, concreteness, query.getDnf()); | ||
28 | return query.withDnf(liftedDnf); | ||
29 | } | ||
30 | |||
31 | public <T> FunctionalQuery<T> lift(Modality modality, Concreteness concreteness, FunctionalQuery<T> query) { | ||
32 | var liftedDnf = lift(modality, concreteness, query.getDnf()); | ||
33 | return query.withDnf(liftedDnf); | ||
34 | } | ||
35 | |||
36 | public Dnf lift(Modality modality, Concreteness concreteness, Dnf dnf) { | ||
37 | return cache.computeIfAbsent(new ModalDnf(modality, concreteness, dnf), this::doLift); | ||
33 | } | 38 | } |
34 | 39 | ||
35 | private Dnf doLift(ModalDnf modalDnf) { | 40 | private Dnf doLift(ModalDnf modalDnf) { |
36 | var modality = modalDnf.modality(); | 41 | var modality = modalDnf.modality(); |
42 | var concreteness = modalDnf.concreteness(); | ||
37 | var dnf = modalDnf.dnf(); | 43 | var dnf = modalDnf.dnf(); |
38 | var builder = Dnf.builder(); | 44 | var builder = Dnf.builder(decorateName(dnf.name(), modality, concreteness)); |
39 | builder.symbolicParameters(dnf.getSymbolicParameters()); | 45 | builder.symbolicParameters(dnf.getSymbolicParameters()); |
40 | boolean changed = false; | 46 | builder.functionalDependencies(dnf.getFunctionalDependencies()); |
41 | for (var clause : dnf.getClauses()) { | 47 | for (var clause : dnf.getClauses()) { |
42 | if (liftClause(modality, dnf, clause, builder)) { | 48 | builder.clause(liftClause(modality, concreteness, dnf, clause)); |
43 | changed = true; | ||
44 | } | ||
45 | } | 49 | } |
46 | if (changed) { | 50 | var liftedDnf = builder.build(); |
47 | return builder.build(); | 51 | if (dnf.equalsWithSubstitution(DnfEqualityChecker.DEFAULT, liftedDnf)) { |
52 | return dnf; | ||
48 | } | 53 | } |
49 | return dnf; | 54 | return liftedDnf; |
50 | } | 55 | } |
51 | 56 | ||
52 | private boolean liftClause(Modality modality, Dnf originalDnf, DnfClause clause, DnfBuilder builder) { | 57 | private List<Literal> liftClause(Modality modality, Concreteness concreteness, Dnf dnf, DnfClause clause) { |
53 | boolean changed = false; | 58 | var lifter = new ClauseLifter(modality, concreteness, dnf, clause); |
54 | var quantifiedVariables = getQuantifiedDataVariables(originalDnf, clause); | 59 | return lifter.liftClause(); |
55 | var literals = clause.literals(); | ||
56 | var liftedLiterals = new ArrayList<Literal>(literals.size()); | ||
57 | for (var literal : literals) { | ||
58 | Literal liftedLiteral = liftLiteral(modality, literal); | ||
59 | if (liftedLiteral == null) { | ||
60 | liftedLiteral = literal; | ||
61 | } else { | ||
62 | changed = true; | ||
63 | } | ||
64 | liftedLiterals.add(liftedLiteral); | ||
65 | var variable = isExistsLiteralForVariable(modality, liftedLiteral); | ||
66 | if (variable != null) { | ||
67 | // If we already quantify over the existence of the variable with the expected modality, | ||
68 | // we don't need to insert quantification manually. | ||
69 | quantifiedVariables.remove(variable); | ||
70 | } | ||
71 | } | ||
72 | for (var quantifiedVariable : quantifiedVariables) { | ||
73 | // Quantify over data variables that are not already quantified with the expected modality. | ||
74 | liftedLiterals.add(new CallLiteral(CallPolarity.POSITIVE, | ||
75 | new ModalConstraint(modality, ReasoningAdapter.EXISTS), List.of(quantifiedVariable))); | ||
76 | } | ||
77 | builder.clause(liftedLiterals); | ||
78 | return changed || !quantifiedVariables.isEmpty(); | ||
79 | } | ||
80 | |||
81 | private static LinkedHashSet<Variable> getQuantifiedDataVariables(Dnf originalDnf, DnfClause clause) { | ||
82 | var quantifiedVariables = new LinkedHashSet<>(clause.positiveVariables()); | ||
83 | for (var symbolicParameter : originalDnf.getSymbolicParameters()) { | ||
84 | // The existence of parameters will be checked outside this DNF. | ||
85 | quantifiedVariables.remove(symbolicParameter.getVariable()); | ||
86 | } | ||
87 | quantifiedVariables.removeIf(variable -> !(variable instanceof NodeVariable)); | ||
88 | return quantifiedVariables; | ||
89 | } | 60 | } |
90 | 61 | ||
91 | @Nullable | 62 | private record ModalDnf(Modality modality, Concreteness concreteness, Dnf dnf) { |
92 | private Variable isExistsLiteralForVariable(Modality modality, Literal literal) { | 63 | @Override |
93 | if (literal instanceof CallLiteral callLiteral && | 64 | public String toString() { |
94 | callLiteral.getPolarity() == CallPolarity.POSITIVE && | 65 | return "%s %s %s".formatted(modality, concreteness, dnf.name()); |
95 | callLiteral.getTarget() instanceof ModalConstraint modalConstraint && | ||
96 | modalConstraint.modality() == modality && | ||
97 | modalConstraint.constraint().equals(ReasoningAdapter.EXISTS)) { | ||
98 | return callLiteral.getArguments().get(0); | ||
99 | } | 66 | } |
100 | return null; | ||
101 | } | 67 | } |
102 | 68 | ||
103 | @Nullable | 69 | public static String decorateName(String name, Modality modality, Concreteness concreteness) { |
104 | private Literal liftLiteral(Modality modality, Literal literal) { | 70 | return "%s#%s#%s".formatted(name, modality, concreteness); |
105 | if (!(literal instanceof CallLiteral callLiteral)) { | ||
106 | return null; | ||
107 | } | ||
108 | var target = callLiteral.getTarget(); | ||
109 | if (target instanceof ModalConstraint modalTarget) { | ||
110 | var actualTarget = modalTarget.constraint(); | ||
111 | if (actualTarget instanceof Dnf dnf) { | ||
112 | var targetModality = modalTarget.modality(); | ||
113 | var liftedTarget = lift(targetModality, dnf); | ||
114 | return new CallLiteral(callLiteral.getPolarity(), liftedTarget, callLiteral.getArguments()); | ||
115 | } | ||
116 | // No more lifting to be done, pass any modal call to a partial symbol through. | ||
117 | return null; | ||
118 | } else if (target instanceof Dnf dnf) { | ||
119 | var polarity = callLiteral.getPolarity(); | ||
120 | var liftedTarget = lift(modality.commute(polarity), dnf); | ||
121 | // Use == instead of equals(), because lift will return the same object by reference is there are no | ||
122 | // changes made during lifting. | ||
123 | return liftedTarget == target ? null : new CallLiteral(polarity, liftedTarget, callLiteral.getArguments()); | ||
124 | } else { | ||
125 | return PartialLiterals.addModality(callLiteral, modality); | ||
126 | } | ||
127 | } | 71 | } |
128 | } | 72 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java deleted file mode 100644 index 16fb8fbf..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.lifting; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Dnf; | ||
9 | import tools.refinery.store.reasoning.literal.Modality; | ||
10 | |||
11 | record ModalDnf(Modality modality, Dnf dnf) { | ||
12 | @Override | ||
13 | public String toString() { | ||
14 | return "%s %s".formatted(modality, dnf.name()); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java new file mode 100644 index 00000000..43ac5904 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java | |||
@@ -0,0 +1,18 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import java.util.Locale; | ||
9 | |||
10 | public enum Concreteness { | ||
11 | PARTIAL, | ||
12 | CANDIDATE; | ||
13 | |||
14 | @Override | ||
15 | public String toString() { | ||
16 | return name().toLowerCase(Locale.ROOT); | ||
17 | } | ||
18 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java new file mode 100644 index 00000000..91dd2b72 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
10 | import tools.refinery.store.query.literal.AbstractCountLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | public class CountCandidateLowerBoundLiteral extends AbstractCountLiteral<Integer> { | ||
19 | public CountCandidateLowerBoundLiteral(DataVariable<Integer> resultVariable, Constraint target, | ||
20 | List<Variable> arguments) { | ||
21 | super(Integer.class, resultVariable, target, arguments); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | protected Integer zero() { | ||
26 | return 0; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | protected Integer one() { | ||
31 | return 1; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
36 | return new CountCandidateLowerBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), | ||
37 | substitutedArguments); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
42 | return new CountCandidateLowerBoundLiteral(getResultVariable(), newTarget, newArguments); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | protected String operatorName() { | ||
47 | return "@LowerBound(\"candidate\") count"; | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java new file mode 100644 index 00000000..94c9399d --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
10 | import tools.refinery.store.query.literal.AbstractCountLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | public class CountCandidateUpperBoundLiteral extends AbstractCountLiteral<Integer> { | ||
19 | public CountCandidateUpperBoundLiteral(DataVariable<Integer> resultVariable, Constraint target, | ||
20 | List<Variable> arguments) { | ||
21 | super(Integer.class, resultVariable, target, arguments); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | protected Integer zero() { | ||
26 | return 0; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | protected Integer one() { | ||
31 | return 1; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
36 | return new CountCandidateUpperBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), | ||
37 | substitutedArguments); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
42 | return new CountCandidateUpperBoundLiteral(getResultVariable(), newTarget, newArguments); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | protected String operatorName() { | ||
47 | return "@UpperBound(\"candidate\") count"; | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java new file mode 100644 index 00000000..b75b0cab --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java | |||
@@ -0,0 +1,49 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
10 | import tools.refinery.store.query.literal.AbstractCountLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | |||
16 | import java.util.List; | ||
17 | |||
18 | public class CountLowerBoundLiteral extends AbstractCountLiteral<Integer> { | ||
19 | public CountLowerBoundLiteral(DataVariable<Integer> resultVariable, Constraint target, | ||
20 | List<Variable> arguments) { | ||
21 | super(Integer.class, resultVariable, target, arguments); | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | protected Integer zero() { | ||
26 | return 0; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | protected Integer one() { | ||
31 | return 1; | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
36 | return new CountLowerBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), | ||
37 | substitutedArguments); | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
42 | return new CountLowerBoundLiteral(getResultVariable(), newTarget, newArguments); | ||
43 | } | ||
44 | |||
45 | @Override | ||
46 | protected String operatorName() { | ||
47 | return "@LowerBound(\"partial\") count"; | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java new file mode 100644 index 00000000..03842143 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.literal; | ||
7 | |||
8 | import tools.refinery.store.query.Constraint; | ||
9 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
10 | import tools.refinery.store.query.literal.AbstractCountLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.substitution.Substitution; | ||
13 | import tools.refinery.store.query.term.DataVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
16 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
17 | |||
18 | import java.util.List; | ||
19 | |||
20 | public class CountUpperBoundLiteral extends AbstractCountLiteral<UpperCardinality> { | ||
21 | public CountUpperBoundLiteral(DataVariable<UpperCardinality> resultVariable, Constraint target, | ||
22 | List<Variable> arguments) { | ||
23 | super(UpperCardinality.class, resultVariable, target, arguments); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | protected UpperCardinality zero() { | ||
28 | return UpperCardinalities.ZERO; | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | protected UpperCardinality one() { | ||
33 | return UpperCardinalities.UNBOUNDED; | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) { | ||
38 | return new CountUpperBoundLiteral(substitution.getTypeSafeSubstitute(getResultVariable()), getTarget(), | ||
39 | substitutedArguments); | ||
40 | } | ||
41 | |||
42 | @Override | ||
43 | public AbstractCallLiteral withArguments(Constraint newTarget, List<Variable> newArguments) { | ||
44 | return new CountUpperBoundLiteral(getResultVariable(), newTarget, newArguments); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | protected String operatorName() { | ||
49 | return "@UpperBound(\"partial\") count"; | ||
50 | } | ||
51 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java index 4e5a6099..2235a95d 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java | |||
@@ -6,18 +6,29 @@ | |||
6 | package tools.refinery.store.reasoning.literal; | 6 | package tools.refinery.store.reasoning.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.Constraint; | 8 | import tools.refinery.store.query.Constraint; |
9 | import tools.refinery.store.query.InvalidQueryException; | ||
9 | import tools.refinery.store.query.equality.LiteralEqualityHelper; | 10 | import tools.refinery.store.query.equality.LiteralEqualityHelper; |
10 | import tools.refinery.store.query.literal.Reduction; | 11 | import tools.refinery.store.query.literal.Reduction; |
11 | import tools.refinery.store.query.term.Parameter; | 12 | import tools.refinery.store.query.term.Parameter; |
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
12 | 14 | ||
13 | import java.util.List; | 15 | import java.util.List; |
14 | 16 | ||
15 | public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint { | 17 | public record ModalConstraint(Modality modality, Concreteness concreteness, Constraint constraint) |
16 | private static final String FORMAT = "%s %s"; | 18 | implements Constraint { |
19 | public ModalConstraint { | ||
20 | if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) { | ||
21 | throw new InvalidQueryException("Already concrete constraints cannot be abstracted"); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | public ModalConstraint(Modality modality, Constraint constraint) { | ||
26 | this(modality, Concreteness.PARTIAL, constraint); | ||
27 | } | ||
17 | 28 | ||
18 | @Override | 29 | @Override |
19 | public String name() { | 30 | public String name() { |
20 | return FORMAT.formatted(modality, constraint.name()); | 31 | return formatName(constraint.name()); |
21 | } | 32 | } |
22 | 33 | ||
23 | @Override | 34 | @Override |
@@ -36,16 +47,33 @@ public record ModalConstraint(Modality modality, Constraint constraint) implemen | |||
36 | return false; | 47 | return false; |
37 | } | 48 | } |
38 | var otherModalConstraint = (ModalConstraint) other; | 49 | var otherModalConstraint = (ModalConstraint) other; |
39 | return modality == otherModalConstraint.modality && constraint.equals(helper, otherModalConstraint.constraint); | 50 | return modality == otherModalConstraint.modality && |
51 | concreteness == otherModalConstraint.concreteness && | ||
52 | constraint.equals(helper, otherModalConstraint.constraint); | ||
40 | } | 53 | } |
41 | 54 | ||
42 | @Override | 55 | @Override |
43 | public String toReferenceString() { | 56 | public String toReferenceString() { |
44 | return FORMAT.formatted(modality, constraint.toReferenceString()); | 57 | return formatName(constraint.toReferenceString()); |
45 | } | 58 | } |
46 | 59 | ||
47 | @Override | 60 | @Override |
48 | public String toString() { | 61 | public String toString() { |
49 | return FORMAT.formatted(modality, constraint); | 62 | return formatName(constraint.toString()); |
63 | } | ||
64 | |||
65 | private String formatName(String constraintName) { | ||
66 | if (concreteness == Concreteness.PARTIAL) { | ||
67 | return "%s %s".formatted(modality, constraintName); | ||
68 | } | ||
69 | return "%s %s %s".formatted(modality, concreteness, constraintName); | ||
70 | } | ||
71 | |||
72 | public static Constraint of(Modality modality, Concreteness concreteness, Constraint constraint) { | ||
73 | if (constraint instanceof AnySymbolView || constraint instanceof ModalConstraint) { | ||
74 | // Symbol views and lifted constraints are already concrete. Thus, they cannot be abstracted at all. | ||
75 | return constraint; | ||
76 | } | ||
77 | return new ModalConstraint(modality, concreteness, constraint); | ||
50 | } | 78 | } |
51 | } | 79 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java index 96466d5c..c99a0399 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java | |||
@@ -11,14 +11,12 @@ import java.util.Locale; | |||
11 | 11 | ||
12 | public enum Modality { | 12 | public enum Modality { |
13 | MUST, | 13 | MUST, |
14 | MAY, | 14 | MAY; |
15 | CURRENT; | ||
16 | 15 | ||
17 | public Modality negate() { | 16 | public Modality negate() { |
18 | return switch(this) { | 17 | return switch(this) { |
19 | case MUST -> MAY; | 18 | case MUST -> MAY; |
20 | case MAY -> MUST; | 19 | case MAY -> MUST; |
21 | case CURRENT -> CURRENT; | ||
22 | }; | 20 | }; |
23 | } | 21 | } |
24 | 22 | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java index 0e46a795..2614c26e 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java | |||
@@ -5,6 +5,7 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.literal; | 6 | package tools.refinery.store.reasoning.literal; |
7 | 7 | ||
8 | import tools.refinery.store.query.InvalidQueryException; | ||
8 | import tools.refinery.store.query.literal.CallLiteral; | 9 | import tools.refinery.store.query.literal.CallLiteral; |
9 | 10 | ||
10 | public final class PartialLiterals { | 11 | public final class PartialLiterals { |
@@ -13,24 +14,28 @@ public final class PartialLiterals { | |||
13 | } | 14 | } |
14 | 15 | ||
15 | public static CallLiteral may(CallLiteral literal) { | 16 | public static CallLiteral may(CallLiteral literal) { |
16 | return addModality(literal, Modality.MAY); | 17 | return addModality(literal, Modality.MAY, Concreteness.PARTIAL); |
17 | } | 18 | } |
18 | 19 | ||
19 | public static CallLiteral must(CallLiteral literal) { | 20 | public static CallLiteral must(CallLiteral literal) { |
20 | return addModality(literal, Modality.MUST); | 21 | return addModality(literal, Modality.MUST, Concreteness.PARTIAL); |
21 | } | 22 | } |
22 | 23 | ||
23 | public static CallLiteral current(CallLiteral literal) { | 24 | public static CallLiteral candidateMay(CallLiteral literal) { |
24 | return addModality(literal, Modality.CURRENT); | 25 | return addModality(literal, Modality.MAY, Concreteness.CANDIDATE); |
25 | } | 26 | } |
26 | 27 | ||
27 | public static CallLiteral addModality(CallLiteral literal, Modality modality) { | 28 | public static CallLiteral candidateMust(CallLiteral literal) { |
29 | return addModality(literal, Modality.MUST, Concreteness.CANDIDATE); | ||
30 | } | ||
31 | |||
32 | public static CallLiteral addModality(CallLiteral literal, Modality modality, Concreteness concreteness) { | ||
28 | var target = literal.getTarget(); | 33 | var target = literal.getTarget(); |
29 | if (target instanceof ModalConstraint) { | 34 | if (target instanceof ModalConstraint) { |
30 | throw new IllegalArgumentException("Literal %s already has modality".formatted(literal)); | 35 | throw new InvalidQueryException("Literal %s already has modality".formatted(literal)); |
31 | } | 36 | } |
32 | var polarity = literal.getPolarity(); | 37 | var polarity = literal.getPolarity(); |
33 | var modalTarget = new ModalConstraint(modality.commute(polarity), target); | 38 | var modalTarget = new ModalConstraint(modality.commute(polarity), concreteness, target); |
34 | return new CallLiteral(polarity, modalTarget, literal.getArguments()); | 39 | return new CallLiteral(polarity, modalTarget, literal.getArguments()); |
35 | } | 40 | } |
36 | } | 41 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java new file mode 100644 index 00000000..a7fc5b7e --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java | |||
@@ -0,0 +1,29 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
10 | |||
11 | public abstract class AbstractPartialInterpretationRefiner<A, C> implements PartialInterpretationRefiner<A, C> { | ||
12 | private final ReasoningAdapter adapter; | ||
13 | private final PartialSymbol<A, C> partialSymbol; | ||
14 | |||
15 | protected AbstractPartialInterpretationRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol) { | ||
16 | this.adapter = adapter; | ||
17 | this.partialSymbol = partialSymbol; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public ReasoningAdapter getAdapter() { | ||
22 | return adapter; | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public PartialSymbol<A, C> getPartialSymbol() { | ||
27 | return partialSymbol; | ||
28 | } | ||
29 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java new file mode 100644 index 00000000..6c381511 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
10 | |||
11 | public sealed interface AnyPartialInterpretationRefiner permits PartialInterpretationRefiner { | ||
12 | ReasoningAdapter getAdapter(); | ||
13 | |||
14 | AnyPartialSymbol getPartialSymbol(); | ||
15 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java new file mode 100644 index 00000000..ebb9b824 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.representation.Symbol; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | import java.util.Objects; | ||
15 | |||
16 | public class ConcreteSymbolRefiner<A, C> extends AbstractPartialInterpretationRefiner<A, C> { | ||
17 | private final Interpretation<A> interpretation; | ||
18 | |||
19 | public ConcreteSymbolRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol, | ||
20 | Symbol<A> concreteSymbol) { | ||
21 | super(adapter, partialSymbol); | ||
22 | interpretation = adapter.getModel().getInterpretation(concreteSymbol); | ||
23 | } | ||
24 | |||
25 | @Override | ||
26 | public boolean merge(Tuple key, A value) { | ||
27 | var currentValue = interpretation.get(key); | ||
28 | var mergedValue = getPartialSymbol().abstractDomain().commonRefinement(currentValue, value); | ||
29 | if (!Objects.equals(currentValue, mergedValue)) { | ||
30 | interpretation.put(key, mergedValue); | ||
31 | } | ||
32 | return true; | ||
33 | } | ||
34 | |||
35 | public static <A1, C1> Factory<A1, C1> of(Symbol<A1> concreteSymbol) { | ||
36 | return (adapter, partialSymbol) -> new ConcreteSymbolRefiner<>(adapter, partialSymbol, concreteSymbol); | ||
37 | } | ||
38 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java new file mode 100644 index 00000000..d4ec2e8b --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java | |||
@@ -0,0 +1,79 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.model.Interpretation; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | |||
13 | public class DefaultStorageRefiner<T> implements StorageRefiner { | ||
14 | private static final StorageRefiner.Factory<Object> FACTORY = DefaultStorageRefiner::new; | ||
15 | |||
16 | private final Interpretation<T> interpretation; | ||
17 | |||
18 | public DefaultStorageRefiner(Symbol<T> symbol, Model model) { | ||
19 | interpretation = model.getInterpretation(symbol); | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean split(int parentNode, int childNode) { | ||
24 | var symbol = interpretation.getSymbol(); | ||
25 | int arity = symbol.arity(); | ||
26 | for (int i = 0; i < arity; i++) { | ||
27 | int adjacentSize = interpretation.getAdjacentSize(i, parentNode); | ||
28 | if (adjacentSize == 0) { | ||
29 | continue; | ||
30 | } | ||
31 | var toSetKeys = new Tuple[adjacentSize]; | ||
32 | // This is safe, because we won't pass the array to the outside. | ||
33 | @SuppressWarnings("unchecked") | ||
34 | var toSetValues = (T[]) new Object[adjacentSize]; | ||
35 | var cursor = interpretation.getAdjacent(i, parentNode); | ||
36 | int j = 0; | ||
37 | while (cursor.move()) { | ||
38 | toSetKeys[j] = cursor.getKey().set(i, childNode); | ||
39 | toSetValues[j] = cursor.getValue(); | ||
40 | j++; | ||
41 | } | ||
42 | for (j = 0; j < adjacentSize; j++) { | ||
43 | interpretation.put(toSetKeys[j], toSetValues[j]); | ||
44 | } | ||
45 | } | ||
46 | return true; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public boolean cleanup(int nodeToDelete) { | ||
51 | var symbol = interpretation.getSymbol(); | ||
52 | int arity = symbol.arity(); | ||
53 | var defaultValue = symbol.defaultValue(); | ||
54 | for (int i = 0; i < arity; i++) { | ||
55 | int adjacentSize = interpretation.getAdjacentSize(i, nodeToDelete); | ||
56 | if (adjacentSize == 0) { | ||
57 | continue; | ||
58 | } | ||
59 | var toDelete = new Tuple[adjacentSize]; | ||
60 | var cursor = interpretation.getAdjacent(i, nodeToDelete); | ||
61 | int j = 0; | ||
62 | while (cursor.move()) { | ||
63 | toDelete[j] = cursor.getKey(); | ||
64 | j++; | ||
65 | } | ||
66 | for (j = 0; j < adjacentSize; j++) { | ||
67 | interpretation.put(toDelete[j], defaultValue); | ||
68 | } | ||
69 | } | ||
70 | return true; | ||
71 | } | ||
72 | |||
73 | public static <T> StorageRefiner.Factory<T> factory() { | ||
74 | // This is safe, because {@code FACTORY} doesn't depend on {@code T} at all. | ||
75 | @SuppressWarnings("unchecked") | ||
76 | var typedFactory = (StorageRefiner.Factory<T>) FACTORY; | ||
77 | return typedFactory; | ||
78 | } | ||
79 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java new file mode 100644 index 00000000..f48d1d1f --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | public non-sealed interface PartialInterpretationRefiner<A, C> extends AnyPartialInterpretationRefiner { | ||
13 | @Override | ||
14 | PartialSymbol<A, C> getPartialSymbol(); | ||
15 | |||
16 | boolean merge(Tuple key, A value); | ||
17 | |||
18 | @FunctionalInterface | ||
19 | interface Factory<A, C> { | ||
20 | PartialInterpretationRefiner<A, C> create(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java new file mode 100644 index 00000000..0c82695c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
10 | |||
11 | @FunctionalInterface | ||
12 | public interface PartialModelInitializer { | ||
13 | void initialize(Model model, ModelSeed modelSeed); | ||
14 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java new file mode 100644 index 00000000..b6bccb01 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java | |||
@@ -0,0 +1,34 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
12 | |||
13 | public class RefinementBasedInitializer<A, C> implements PartialModelInitializer { | ||
14 | private final PartialSymbol<A, C> partialSymbol; | ||
15 | |||
16 | public RefinementBasedInitializer(PartialSymbol<A, C> partialSymbol) { | ||
17 | this.partialSymbol = partialSymbol; | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public void initialize(Model model, ModelSeed modelSeed) { | ||
22 | var refiner = model.getAdapter(ReasoningAdapter.class).getRefiner(partialSymbol); | ||
23 | var defaultValue = partialSymbol.abstractDomain().unknown(); | ||
24 | var cursor = modelSeed.getCursor(partialSymbol, defaultValue); | ||
25 | while (cursor.move()) { | ||
26 | var key = cursor.getKey(); | ||
27 | var value = cursor.getValue(); | ||
28 | if (!refiner.merge(key, value)) { | ||
29 | throw new IllegalArgumentException("Failed to merge value %s for key %s into %s" | ||
30 | .formatted(value, key, partialSymbol)); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java new file mode 100644 index 00000000..004696fd --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java | |||
@@ -0,0 +1,20 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.refinement; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | |||
11 | public interface StorageRefiner { | ||
12 | boolean split(int parentNode, int childNode); | ||
13 | |||
14 | boolean cleanup(int nodeToDelete); | ||
15 | |||
16 | @FunctionalInterface | ||
17 | interface Factory<T> { | ||
18 | StorageRefiner create(Symbol<T> symbol, Model model); | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java index d58d026f..e59c8af8 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java | |||
@@ -15,11 +15,6 @@ public record PartialFunction<A, C>(String name, int arity, AbstractDomain<A, C> | |||
15 | } | 15 | } |
16 | 16 | ||
17 | @Override | 17 | @Override |
18 | public C defaultConcreteValue() { | ||
19 | return null; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean equals(Object o) { | 18 | public boolean equals(Object o) { |
24 | return this == o; | 19 | return this == o; |
25 | } | 20 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java index 6b2f050b..4ccb7033 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java | |||
@@ -26,11 +26,6 @@ public record PartialRelation(String name, int arity) implements PartialSymbol<T | |||
26 | } | 26 | } |
27 | 27 | ||
28 | @Override | 28 | @Override |
29 | public Boolean defaultConcreteValue() { | ||
30 | return false; | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public List<Parameter> getParameters() { | 29 | public List<Parameter> getParameters() { |
35 | var parameters = new Parameter[arity]; | 30 | var parameters = new Parameter[arity]; |
36 | Arrays.fill(parameters, Parameter.NODE_OUT); | 31 | Arrays.fill(parameters, Parameter.NODE_OUT); |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java index 3a08bdd8..38b2e466 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java | |||
@@ -13,5 +13,11 @@ public sealed interface PartialSymbol<A, C> extends AnyPartialSymbol permits Par | |||
13 | 13 | ||
14 | A defaultValue(); | 14 | A defaultValue(); |
15 | 15 | ||
16 | C defaultConcreteValue(); | 16 | static PartialRelation of(String name, int arity) { |
17 | return new PartialRelation(name, arity); | ||
18 | } | ||
19 | |||
20 | static <A, C> PartialFunction<A, C> of(String name, int arity, AbstractDomain<A, C> abstractDomain) { | ||
21 | return new PartialFunction<>(name, arity, abstractDomain); | ||
22 | } | ||
17 | } | 23 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java new file mode 100644 index 00000000..8b1c3bb3 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java | |||
@@ -0,0 +1,111 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.map.Cursors; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Map; | ||
13 | import java.util.Objects; | ||
14 | |||
15 | record MapBasedSeed<T>(int arity, Class<T> valueType, T reducedValue, Map<Tuple, T> map) implements Seed<T> { | ||
16 | @Override | ||
17 | public T get(Tuple key) { | ||
18 | var value = map.get(key); | ||
19 | return value == null ? reducedValue : value; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { | ||
24 | if (Objects.equals(defaultValue, reducedValue)) { | ||
25 | return Cursors.of(map); | ||
26 | } | ||
27 | return new CartesianProductCursor<>(arity, nodeCount, reducedValue, defaultValue, map); | ||
28 | } | ||
29 | |||
30 | private static class CartesianProductCursor<T> implements Cursor<Tuple, T> { | ||
31 | private final int nodeCount; | ||
32 | private final T reducedValue; | ||
33 | private final T defaultValue; | ||
34 | private final Map<Tuple, T> map; | ||
35 | private final int[] counter; | ||
36 | private State state = State.INITIAL; | ||
37 | private Tuple key; | ||
38 | private T value; | ||
39 | |||
40 | private CartesianProductCursor(int arity, int nodeCount, T reducedValue, T defaultValue, Map<Tuple, T> map) { | ||
41 | this.nodeCount = nodeCount; | ||
42 | this.reducedValue = reducedValue; | ||
43 | this.defaultValue = defaultValue; | ||
44 | this.map = map; | ||
45 | counter = new int[arity]; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public Tuple getKey() { | ||
50 | return key; | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public T getValue() { | ||
55 | return value; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public boolean isTerminated() { | ||
60 | return state == State.TERMINATED; | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public boolean move() { | ||
65 | return switch (state) { | ||
66 | case INITIAL -> { | ||
67 | state = State.STARTED; | ||
68 | yield checkValue() || moveToNext(); | ||
69 | } | ||
70 | case STARTED -> moveToNext(); | ||
71 | case TERMINATED -> false; | ||
72 | }; | ||
73 | } | ||
74 | |||
75 | private boolean moveToNext() { | ||
76 | do { | ||
77 | increment(); | ||
78 | } while (state != State.TERMINATED && !checkValue()); | ||
79 | return state != State.TERMINATED; | ||
80 | } | ||
81 | |||
82 | private void increment() { | ||
83 | int i = counter.length - 1; | ||
84 | while (i >= 0) { | ||
85 | counter[i]++; | ||
86 | if (counter[i] < nodeCount) { | ||
87 | return; | ||
88 | } | ||
89 | counter[i] = 0; | ||
90 | i--; | ||
91 | } | ||
92 | state = State.TERMINATED; | ||
93 | } | ||
94 | |||
95 | private boolean checkValue() { | ||
96 | key = Tuple.of(counter); | ||
97 | var valueInMap = map.get(key); | ||
98 | if (Objects.equals(valueInMap, defaultValue)) { | ||
99 | return false; | ||
100 | } | ||
101 | value = valueInMap == null ? reducedValue : valueInMap; | ||
102 | return true; | ||
103 | } | ||
104 | |||
105 | private enum State { | ||
106 | INITIAL, | ||
107 | STARTED, | ||
108 | TERMINATED | ||
109 | } | ||
110 | } | ||
111 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java new file mode 100644 index 00000000..e6b3eaf9 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java | |||
@@ -0,0 +1,95 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.tuple.Tuple; | ||
12 | |||
13 | import java.util.Collections; | ||
14 | import java.util.LinkedHashMap; | ||
15 | import java.util.Map; | ||
16 | import java.util.Set; | ||
17 | import java.util.function.Consumer; | ||
18 | |||
19 | public class ModelSeed { | ||
20 | private final int nodeCount; | ||
21 | private final Map<AnyPartialSymbol, Seed<?>> seeds; | ||
22 | |||
23 | private ModelSeed(int nodeCount, Map<AnyPartialSymbol, Seed<?>> seeds) { | ||
24 | this.nodeCount = nodeCount; | ||
25 | this.seeds = seeds; | ||
26 | } | ||
27 | |||
28 | public int getNodeCount() { | ||
29 | return nodeCount; | ||
30 | } | ||
31 | |||
32 | public <A> Seed<A> getSeed(PartialSymbol<A, ?> partialSymbol) { | ||
33 | var seed = seeds.get(partialSymbol); | ||
34 | if (seed == null) { | ||
35 | throw new IllegalArgumentException("No seed for partial symbol " + partialSymbol); | ||
36 | } | ||
37 | // The builder makes sure only well-typed seeds can be added. | ||
38 | @SuppressWarnings("unchecked") | ||
39 | var typedSeed = (Seed<A>) seed; | ||
40 | return typedSeed; | ||
41 | } | ||
42 | |||
43 | public boolean containsSeed(AnyPartialSymbol symbol) { | ||
44 | return seeds.containsKey(symbol); | ||
45 | } | ||
46 | |||
47 | public Set<AnyPartialSymbol> getSeededSymbols() { | ||
48 | return Collections.unmodifiableSet(seeds.keySet()); | ||
49 | } | ||
50 | |||
51 | public <A> Cursor<Tuple, A> getCursor(PartialSymbol<A, ?> partialSymbol, A defaultValue) { | ||
52 | return getSeed(partialSymbol).getCursor(defaultValue, nodeCount); | ||
53 | } | ||
54 | |||
55 | public static Builder builder(int nodeCount) { | ||
56 | return new Builder(nodeCount); | ||
57 | } | ||
58 | |||
59 | public static class Builder { | ||
60 | private final int nodeCount; | ||
61 | private final Map<AnyPartialSymbol, Seed<?>> seeds = new LinkedHashMap<>(); | ||
62 | |||
63 | private Builder(int nodeCount) { | ||
64 | if (nodeCount < 0) { | ||
65 | throw new IllegalArgumentException("Node count must not be negative"); | ||
66 | } | ||
67 | this.nodeCount = nodeCount; | ||
68 | } | ||
69 | |||
70 | public <A> Builder seed(PartialSymbol<A, ?> partialSymbol, Seed<A> seed) { | ||
71 | if (seed.arity() != partialSymbol.arity()) { | ||
72 | throw new IllegalStateException("Expected seed of arity %d for partial symbol %s, but got %d instead" | ||
73 | .formatted(partialSymbol.arity(), partialSymbol, seed.arity())); | ||
74 | } | ||
75 | if (!seed.valueType().equals(partialSymbol.abstractDomain().abstractType())) { | ||
76 | throw new IllegalStateException("Expected seed of type %s for partial symbol %s, but got %s instead" | ||
77 | .formatted(partialSymbol.abstractDomain().abstractType(), partialSymbol, seed.valueType())); | ||
78 | } | ||
79 | if (seeds.put(partialSymbol, seed) != null) { | ||
80 | throw new IllegalArgumentException("Duplicate seed for partial symbol " + partialSymbol); | ||
81 | } | ||
82 | return this; | ||
83 | } | ||
84 | |||
85 | public <A> Builder seed(PartialSymbol<A, ?> partialSymbol, Consumer<Seed.Builder<A>> callback) { | ||
86 | var builder = Seed.builder(partialSymbol); | ||
87 | callback.accept(builder); | ||
88 | return seed(partialSymbol, builder.build()); | ||
89 | } | ||
90 | |||
91 | public ModelSeed build() { | ||
92 | return new ModelSeed(nodeCount, Collections.unmodifiableMap(seeds)); | ||
93 | } | ||
94 | } | ||
95 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java index 08079f12..732efbcc 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java | |||
@@ -6,14 +6,73 @@ | |||
6 | package tools.refinery.store.reasoning.seed; | 6 | package tools.refinery.store.reasoning.seed; |
7 | 7 | ||
8 | import tools.refinery.store.map.Cursor; | 8 | import tools.refinery.store.map.Cursor; |
9 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
9 | import tools.refinery.store.tuple.Tuple; | 11 | import tools.refinery.store.tuple.Tuple; |
10 | 12 | ||
13 | import java.util.Collections; | ||
14 | import java.util.LinkedHashMap; | ||
15 | import java.util.Map; | ||
16 | |||
11 | public interface Seed<T> { | 17 | public interface Seed<T> { |
12 | int arity(); | 18 | int arity(); |
13 | 19 | ||
20 | Class<T> valueType(); | ||
21 | |||
14 | T reducedValue(); | 22 | T reducedValue(); |
15 | 23 | ||
16 | T get(Tuple key); | 24 | T get(Tuple key); |
17 | 25 | ||
18 | Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount); | 26 | Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount); |
27 | |||
28 | static <T> Builder<T> builder(int arity, Class<T> valueType, T reducedValue) { | ||
29 | return new Builder<>(arity, valueType, reducedValue); | ||
30 | } | ||
31 | |||
32 | static <T> Builder<T> builder(Symbol<T> symbol) { | ||
33 | return builder(symbol.arity(), symbol.valueType(), symbol.defaultValue()); | ||
34 | } | ||
35 | |||
36 | static <T> Builder<T> builder(PartialSymbol<T, ?> partialSymbol) { | ||
37 | return builder(partialSymbol.arity(), partialSymbol.abstractDomain().abstractType(), | ||
38 | partialSymbol.defaultValue()); | ||
39 | } | ||
40 | |||
41 | @SuppressWarnings("UnusedReturnValue") | ||
42 | class Builder<T> { | ||
43 | private final int arity; | ||
44 | private final Class<T> valueType; | ||
45 | private T reducedValue; | ||
46 | private final Map<Tuple, T> map = new LinkedHashMap<>(); | ||
47 | |||
48 | private Builder(int arity, Class<T> valueType, T reducedValue) { | ||
49 | this.arity = arity; | ||
50 | this.valueType = valueType; | ||
51 | this.reducedValue = reducedValue; | ||
52 | } | ||
53 | |||
54 | public Builder<T> reducedValue(T reducedValue) { | ||
55 | this.reducedValue = reducedValue; | ||
56 | return this; | ||
57 | } | ||
58 | |||
59 | public Builder<T> put(Tuple key, T value) { | ||
60 | if (key.getSize() != arity) { | ||
61 | throw new IllegalArgumentException("Expected %s to have %d elements".formatted(key, arity)); | ||
62 | } | ||
63 | map.put(key, value); | ||
64 | return this; | ||
65 | } | ||
66 | |||
67 | public Builder<T> putAll(Map<Tuple, T> map) { | ||
68 | for (var entry : map.entrySet()) { | ||
69 | put(entry.getKey(), entry.getValue()); | ||
70 | } | ||
71 | return this; | ||
72 | } | ||
73 | |||
74 | public Seed<T> build() { | ||
75 | return new MapBasedSeed<>(arity, valueType, reducedValue, Collections.unmodifiableMap(map)); | ||
76 | } | ||
77 | } | ||
19 | } | 78 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java new file mode 100644 index 00000000..9af457d8 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java | |||
@@ -0,0 +1,28 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
10 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
11 | import tools.refinery.store.representation.Symbol; | ||
12 | |||
13 | public class SeedInitializer<T> implements PartialModelInitializer { | ||
14 | private final Symbol<T> symbol; | ||
15 | private final PartialSymbol<T, ?> partialSymbol; | ||
16 | |||
17 | public SeedInitializer(Symbol<T> symbol, PartialSymbol<T, ?> partialSymbol) { | ||
18 | this.symbol = symbol; | ||
19 | this.partialSymbol = partialSymbol; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public void initialize(Model model, ModelSeed modelSeed) { | ||
24 | var interpretation = model.getInterpretation(symbol); | ||
25 | var cursor = modelSeed.getCursor(partialSymbol, symbol.defaultValue()); | ||
26 | interpretation.putAll(cursor); | ||
27 | } | ||
28 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java deleted file mode 100644 index 451d1513..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.seed; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.tuple.Tuple; | ||
10 | |||
11 | public record UniformSeed<T>(int arity, T reducedValue) implements Seed<T> { | ||
12 | public UniformSeed { | ||
13 | if (arity < 0) { | ||
14 | throw new IllegalArgumentException("Arity must not be negative"); | ||
15 | } | ||
16 | } | ||
17 | |||
18 | @Override | ||
19 | public T get(Tuple key) { | ||
20 | return reducedValue; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) { | ||
25 | return null; | ||
26 | } | ||
27 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java deleted file mode 100644 index d6a9e02c..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java +++ /dev/null | |||
@@ -1,159 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.query.substitution.Substitution; | ||
9 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.query.term.Variable; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | |||
14 | import java.util.*; | ||
15 | |||
16 | public final class Advice { | ||
17 | private final AnyPartialSymbol source; | ||
18 | private final PartialRelation target; | ||
19 | private final AdviceSlot slot; | ||
20 | private final boolean mandatory; | ||
21 | private final List<Variable> parameters; | ||
22 | private final List<Literal> literals; | ||
23 | private boolean processed; | ||
24 | |||
25 | public Advice(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot, boolean mandatory, List<Variable> parameters, List<Literal> literals) { | ||
26 | if (mandatory && !slot.isMonotonic()) { | ||
27 | throw new IllegalArgumentException("Only monotonic advice can be mandatory"); | ||
28 | } | ||
29 | this.source = source; | ||
30 | this.target = target; | ||
31 | this.slot = slot; | ||
32 | this.mandatory = mandatory; | ||
33 | checkArity(parameters); | ||
34 | this.parameters = parameters; | ||
35 | this.literals = literals; | ||
36 | } | ||
37 | |||
38 | public AnyPartialSymbol source() { | ||
39 | return source; | ||
40 | } | ||
41 | |||
42 | public PartialRelation target() { | ||
43 | return target; | ||
44 | } | ||
45 | |||
46 | public AdviceSlot slot() { | ||
47 | return slot; | ||
48 | } | ||
49 | |||
50 | public boolean mandatory() { | ||
51 | return mandatory; | ||
52 | } | ||
53 | |||
54 | public List<Variable> parameters() { | ||
55 | return parameters; | ||
56 | } | ||
57 | |||
58 | public List<Literal> literals() { | ||
59 | return literals; | ||
60 | } | ||
61 | |||
62 | public boolean processed() { | ||
63 | return processed; | ||
64 | } | ||
65 | |||
66 | public List<Literal> substitute(List<Variable> substituteParameters) { | ||
67 | checkArity(substituteParameters); | ||
68 | markProcessed(); | ||
69 | // Use a renewing substitution to remove any non-parameter variables and avoid clashed between variables | ||
70 | // coming from different advice in the same clause. | ||
71 | var substitution = Substitution.builder().putManyChecked(parameters, substituteParameters).renewing().build(); | ||
72 | return literals.stream().map(literal -> literal.substitute(substitution)).toList(); | ||
73 | } | ||
74 | |||
75 | private void markProcessed() { | ||
76 | processed = true; | ||
77 | } | ||
78 | |||
79 | public void checkProcessed() { | ||
80 | if (mandatory && !processed) { | ||
81 | throw new IllegalStateException("Mandatory advice %s was not processed".formatted(this)); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | private void checkArity(List<Variable> toCheck) { | ||
86 | if (toCheck.size() != target.arity()) { | ||
87 | throw new IllegalArgumentException("%s needs %d parameters, but got %s".formatted(target.name(), | ||
88 | target.arity(), parameters.size())); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | public static Builder builderFor(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) { | ||
93 | return new Builder(source, target, slot); | ||
94 | } | ||
95 | |||
96 | |||
97 | @Override | ||
98 | public String toString() { | ||
99 | return "Advice[source=%s, target=%s, slot=%s, mandatory=%s, parameters=%s, literals=%s]".formatted(source, | ||
100 | target, slot, mandatory, parameters, literals); | ||
101 | } | ||
102 | |||
103 | public static class Builder { | ||
104 | private final AnyPartialSymbol source; | ||
105 | private final PartialRelation target; | ||
106 | private final AdviceSlot slot; | ||
107 | private boolean mandatory; | ||
108 | private final List<Variable> parameters = new ArrayList<>(); | ||
109 | private final List<Literal> literals = new ArrayList<>(); | ||
110 | |||
111 | private Builder(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) { | ||
112 | this.source = source; | ||
113 | this.target = target; | ||
114 | this.slot = slot; | ||
115 | } | ||
116 | |||
117 | public Builder mandatory(boolean mandatory) { | ||
118 | this.mandatory = mandatory; | ||
119 | return this; | ||
120 | } | ||
121 | |||
122 | public Builder mandatory() { | ||
123 | return mandatory(false); | ||
124 | } | ||
125 | |||
126 | public Builder parameters(List<Variable> variables) { | ||
127 | parameters.addAll(variables); | ||
128 | return this; | ||
129 | } | ||
130 | |||
131 | public Builder parameters(Variable... variables) { | ||
132 | return parameters(List.of(variables)); | ||
133 | } | ||
134 | |||
135 | public Builder parameter(Variable variable) { | ||
136 | parameters.add(variable); | ||
137 | return this; | ||
138 | } | ||
139 | |||
140 | public Builder literals(Collection<Literal> literals) { | ||
141 | this.literals.addAll(literals); | ||
142 | return this; | ||
143 | } | ||
144 | |||
145 | public Builder literals(Literal... literals) { | ||
146 | return literals(List.of(literals)); | ||
147 | } | ||
148 | |||
149 | public Builder literal(Literal literal) { | ||
150 | literals.add(literal); | ||
151 | return this; | ||
152 | } | ||
153 | |||
154 | public Advice build() { | ||
155 | return new Advice(source, target, slot, mandatory, Collections.unmodifiableList(parameters), | ||
156 | Collections.unmodifiableList(literals)); | ||
157 | } | ||
158 | } | ||
159 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java deleted file mode 100644 index bab20340..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.representation.TruthValue; | ||
9 | |||
10 | public enum AdviceSlot { | ||
11 | EXTEND_MUST(true), | ||
12 | |||
13 | RESTRICT_MAY(true), | ||
14 | |||
15 | /** | ||
16 | * Same as {@link #RESTRICT_MAY}, but only active if the value of the relation is not {@link TruthValue#TRUE} or | ||
17 | * {@link TruthValue#ERROR}. | ||
18 | */ | ||
19 | RESTRICT_NEW(false); | ||
20 | |||
21 | private final boolean monotonic; | ||
22 | |||
23 | AdviceSlot(boolean monotonic) { | ||
24 | this.monotonic = monotonic; | ||
25 | } | ||
26 | |||
27 | public boolean isMonotonic() { | ||
28 | return monotonic; | ||
29 | } | ||
30 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java new file mode 100644 index 00000000..48c84348 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStoreBuilder; | ||
9 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
10 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
11 | |||
12 | public sealed interface AnyPartialSymbolTranslator extends ModelStoreConfiguration permits PartialSymbolTranslator { | ||
13 | AnyPartialSymbol getPartialSymbol(); | ||
14 | |||
15 | void configure(ModelStoreBuilder storeBuilder); | ||
16 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java new file mode 100644 index 00000000..c2039afc --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java | |||
@@ -0,0 +1,390 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.Rule; | ||
9 | import tools.refinery.store.dse.transition.objectives.Criteria; | ||
10 | import tools.refinery.store.dse.transition.objectives.Criterion; | ||
11 | import tools.refinery.store.dse.transition.objectives.Objective; | ||
12 | import tools.refinery.store.dse.transition.objectives.Objectives; | ||
13 | import tools.refinery.store.model.ModelStoreBuilder; | ||
14 | import tools.refinery.store.query.Constraint; | ||
15 | import tools.refinery.store.query.dnf.Query; | ||
16 | import tools.refinery.store.query.dnf.QueryBuilder; | ||
17 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
18 | import tools.refinery.store.query.literal.Literal; | ||
19 | import tools.refinery.store.query.term.NodeVariable; | ||
20 | import tools.refinery.store.query.view.MayView; | ||
21 | import tools.refinery.store.query.view.MustView; | ||
22 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
23 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
24 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
25 | import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter; | ||
26 | import tools.refinery.store.reasoning.interpretation.QueryBasedRelationInterpretationFactory; | ||
27 | import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter; | ||
28 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
29 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
30 | import tools.refinery.store.reasoning.literal.Modality; | ||
31 | import tools.refinery.store.reasoning.literal.PartialLiterals; | ||
32 | import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner; | ||
33 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
34 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
35 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
36 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
37 | import tools.refinery.store.representation.AnySymbol; | ||
38 | import tools.refinery.store.representation.Symbol; | ||
39 | import tools.refinery.store.representation.TruthValue; | ||
40 | |||
41 | import java.util.ArrayList; | ||
42 | import java.util.function.BiConsumer; | ||
43 | |||
44 | import static tools.refinery.store.query.literal.Literals.not; | ||
45 | |||
46 | @SuppressWarnings("UnusedReturnValue") | ||
47 | public final class PartialRelationTranslator extends PartialSymbolTranslator<TruthValue, Boolean> { | ||
48 | private final PartialRelation partialRelation; | ||
49 | private PartialRelationRewriter rewriter; | ||
50 | private RelationalQuery query; | ||
51 | private RelationalQuery may; | ||
52 | private RelationalQuery must; | ||
53 | private RelationalQuery candidateMay; | ||
54 | private RelationalQuery candidateMust; | ||
55 | private RoundingMode roundingMode; | ||
56 | |||
57 | private PartialRelationTranslator(PartialRelation partialRelation) { | ||
58 | super(partialRelation); | ||
59 | this.partialRelation = partialRelation; | ||
60 | } | ||
61 | |||
62 | public PartialRelation getPartialRelation() { | ||
63 | return partialRelation; | ||
64 | } | ||
65 | |||
66 | @Override | ||
67 | public PartialRelationTranslator symbol(AnySymbol storageSymbol) { | ||
68 | super.symbol(storageSymbol); | ||
69 | return this; | ||
70 | } | ||
71 | |||
72 | @Override | ||
73 | public <T> PartialRelationTranslator symbol(Symbol<T> storageSymbol, | ||
74 | StorageRefiner.Factory<T> storageRefiner) { | ||
75 | super.symbol(storageSymbol, storageRefiner); | ||
76 | return this; | ||
77 | } | ||
78 | |||
79 | @Override | ||
80 | public PartialRelationTranslator interpretation( | ||
81 | PartialInterpretation.Factory<TruthValue, Boolean> interpretationFactory) { | ||
82 | super.interpretation(interpretationFactory); | ||
83 | return this; | ||
84 | } | ||
85 | |||
86 | @Override | ||
87 | public PartialRelationTranslator refiner( | ||
88 | PartialInterpretationRefiner.Factory<TruthValue, Boolean> interpretationRefiner) { | ||
89 | super.refiner(interpretationRefiner); | ||
90 | return this; | ||
91 | } | ||
92 | |||
93 | public PartialRelationTranslator rewriter(PartialRelationRewriter rewriter) { | ||
94 | checkNotConfigured(); | ||
95 | if (this.rewriter != null) { | ||
96 | throw new IllegalArgumentException("Rewriter was already set"); | ||
97 | } | ||
98 | this.rewriter = rewriter; | ||
99 | return this; | ||
100 | } | ||
101 | |||
102 | @Override | ||
103 | public PartialRelationTranslator initializer(PartialModelInitializer initializer) { | ||
104 | super.initializer(initializer); | ||
105 | return this; | ||
106 | } | ||
107 | |||
108 | @Override | ||
109 | public PartialRelationTranslator decision(Rule decisionRule) { | ||
110 | super.decision(decisionRule); | ||
111 | return this; | ||
112 | } | ||
113 | |||
114 | @Override | ||
115 | public PartialRelationTranslator accept(Criterion acceptanceCriterion) { | ||
116 | super.accept(acceptanceCriterion); | ||
117 | return this; | ||
118 | } | ||
119 | |||
120 | @Override | ||
121 | public PartialRelationTranslator exclude(Criterion exclusionCriterion) { | ||
122 | super.exclude(exclusionCriterion); | ||
123 | return this; | ||
124 | } | ||
125 | |||
126 | @Override | ||
127 | public PartialRelationTranslator objective(Objective objective) { | ||
128 | super.objective(objective); | ||
129 | return this; | ||
130 | } | ||
131 | |||
132 | public PartialRelationTranslator query(RelationalQuery query) { | ||
133 | checkNotConfigured(); | ||
134 | if (this.query != null) { | ||
135 | throw new IllegalArgumentException("Query was already set"); | ||
136 | } | ||
137 | this.query = query; | ||
138 | return this; | ||
139 | } | ||
140 | |||
141 | public PartialRelationTranslator may(RelationalQuery may) { | ||
142 | checkNotConfigured(); | ||
143 | if (this.may != null) { | ||
144 | throw new IllegalArgumentException("May query was already set"); | ||
145 | } | ||
146 | this.may = may; | ||
147 | return this; | ||
148 | } | ||
149 | |||
150 | public PartialRelationTranslator mayNever() { | ||
151 | var never = createQuery(partialRelation.name() + "#never", (builder, parameters) -> {}); | ||
152 | may(never); | ||
153 | return this; | ||
154 | } | ||
155 | |||
156 | public PartialRelationTranslator must(RelationalQuery must) { | ||
157 | checkNotConfigured(); | ||
158 | if (this.must != null) { | ||
159 | throw new IllegalArgumentException("Must query was already set"); | ||
160 | } | ||
161 | this.must = must; | ||
162 | return this; | ||
163 | } | ||
164 | |||
165 | public PartialRelationTranslator candidate(RelationalQuery candidate) { | ||
166 | candidateMay(candidate); | ||
167 | candidateMust(candidate); | ||
168 | return this; | ||
169 | } | ||
170 | |||
171 | public PartialRelationTranslator candidateMay(RelationalQuery candidateMay) { | ||
172 | checkNotConfigured(); | ||
173 | if (this.candidateMay != null) { | ||
174 | throw new IllegalArgumentException("Candidate may query was already set"); | ||
175 | } | ||
176 | this.candidateMay = candidateMay; | ||
177 | return this; | ||
178 | } | ||
179 | |||
180 | public PartialRelationTranslator candidateMust(RelationalQuery candidateMust) { | ||
181 | checkNotConfigured(); | ||
182 | if (this.candidateMust != null) { | ||
183 | throw new IllegalArgumentException("Candidate must query was already set"); | ||
184 | } | ||
185 | this.candidateMust = candidateMust; | ||
186 | return this; | ||
187 | } | ||
188 | |||
189 | public PartialRelationTranslator roundingMode(RoundingMode roundingMode) { | ||
190 | checkNotConfigured(); | ||
191 | if (this.roundingMode != null) { | ||
192 | throw new IllegalArgumentException("Rounding mode was already set"); | ||
193 | } | ||
194 | this.roundingMode = roundingMode; | ||
195 | return this; | ||
196 | } | ||
197 | |||
198 | @Override | ||
199 | protected void doConfigure(ModelStoreBuilder storeBuilder) { | ||
200 | setFallbackRoundingMode(); | ||
201 | createFallbackQueryFromRewriter(); | ||
202 | liftQueries(storeBuilder); | ||
203 | createFallbackQueriesFromSymbol(); | ||
204 | setFallbackCandidateQueries(); | ||
205 | createFallbackRewriter(); | ||
206 | createFallbackInterpretation(); | ||
207 | createFallbackRefiner(); | ||
208 | createFallbackExclude(); | ||
209 | createFallbackObjective(); | ||
210 | super.doConfigure(storeBuilder); | ||
211 | } | ||
212 | |||
213 | private void setFallbackRoundingMode() { | ||
214 | if (roundingMode == null) { | ||
215 | roundingMode = query == null && storageSymbol != null ? RoundingMode.PREFER_FALSE : RoundingMode.NONE; | ||
216 | } | ||
217 | } | ||
218 | |||
219 | private RelationalQuery createQuery(String name, BiConsumer<QueryBuilder, NodeVariable[]> callback) { | ||
220 | int arity = partialRelation.arity(); | ||
221 | var queryBuilder = Query.builder(name); | ||
222 | var parameters = new NodeVariable[arity]; | ||
223 | for (int i = 0; i < arity; i++) { | ||
224 | parameters[i] = queryBuilder.parameter("p" + 1); | ||
225 | } | ||
226 | callback.accept(queryBuilder, parameters); | ||
227 | return queryBuilder.build(); | ||
228 | } | ||
229 | |||
230 | private RelationalQuery createQuery(String name, Constraint constraint) { | ||
231 | return createQuery(name, (builder, parameters) -> builder.clause(constraint.call(parameters))); | ||
232 | } | ||
233 | |||
234 | private void createFallbackQueryFromRewriter() { | ||
235 | if (rewriter != null && query == null) { | ||
236 | query = createQuery(partialRelation.name(), partialRelation); | ||
237 | } | ||
238 | } | ||
239 | |||
240 | private void createFallbackQueriesFromSymbol() { | ||
241 | if (storageSymbol == null || storageSymbol.valueType() != TruthValue.class) { | ||
242 | return; | ||
243 | } | ||
244 | // We checked in the guard clause that this is safe. | ||
245 | @SuppressWarnings("unchecked") | ||
246 | var typedStorageSymbol = (Symbol<TruthValue>) storageSymbol; | ||
247 | var defaultValue = typedStorageSymbol.defaultValue(); | ||
248 | if (may == null && !defaultValue.may()) { | ||
249 | may = createQuery(DnfLifter.decorateName(partialRelation.name(), Modality.MAY, Concreteness.PARTIAL), | ||
250 | new MayView(typedStorageSymbol)); | ||
251 | } | ||
252 | if (must == null && !defaultValue.must()) { | ||
253 | must = createQuery(DnfLifter.decorateName(partialRelation.name(), Modality.MUST, Concreteness.PARTIAL), | ||
254 | new MustView(typedStorageSymbol)); | ||
255 | } | ||
256 | } | ||
257 | |||
258 | private void liftQueries(ModelStoreBuilder storeBuilder) { | ||
259 | if (rewriter instanceof QueryBasedRelationRewriter queryBasedRelationRewriter) { | ||
260 | liftQueriesFromQueryBasedRewriter(queryBasedRelationRewriter); | ||
261 | } else if (query != null) { | ||
262 | liftQueriesFromFourValuedQuery(storeBuilder); | ||
263 | } | ||
264 | } | ||
265 | |||
266 | private void liftQueriesFromQueryBasedRewriter(QueryBasedRelationRewriter queryBasedRelationRewriter) { | ||
267 | if (may == null) { | ||
268 | may = queryBasedRelationRewriter.getMay(); | ||
269 | } | ||
270 | if (must == null) { | ||
271 | must = queryBasedRelationRewriter.getMust(); | ||
272 | } | ||
273 | if (candidateMay == null) { | ||
274 | candidateMay = queryBasedRelationRewriter.getCandidateMay(); | ||
275 | } | ||
276 | if (candidateMust == null) { | ||
277 | candidateMust = queryBasedRelationRewriter.getCandidateMust(); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | private void liftQueriesFromFourValuedQuery(ModelStoreBuilder storeBuilder) { | ||
282 | var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class); | ||
283 | if (may == null) { | ||
284 | may = reasoningBuilder.lift(Modality.MAY, Concreteness.PARTIAL, query); | ||
285 | } | ||
286 | if (must == null) { | ||
287 | must = reasoningBuilder.lift(Modality.MUST, Concreteness.PARTIAL, query); | ||
288 | } | ||
289 | if (candidateMay == null) { | ||
290 | candidateMay = reasoningBuilder.lift(Modality.MAY, Concreteness.CANDIDATE, query); | ||
291 | } | ||
292 | if (candidateMust == null) { | ||
293 | candidateMust = reasoningBuilder.lift(Modality.MAY, Concreteness.CANDIDATE, query); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | private void setFallbackCandidateQueries() { | ||
298 | if (candidateMay == null) { | ||
299 | candidateMay = switch (roundingMode) { | ||
300 | case NONE, PREFER_TRUE -> may; | ||
301 | case PREFER_FALSE -> must; | ||
302 | }; | ||
303 | } | ||
304 | if (candidateMust == null) { | ||
305 | candidateMust = switch (roundingMode) { | ||
306 | case NONE, PREFER_FALSE -> must; | ||
307 | case PREFER_TRUE -> may; | ||
308 | }; | ||
309 | } | ||
310 | } | ||
311 | |||
312 | private void createFallbackRewriter() { | ||
313 | if (rewriter == null) { | ||
314 | rewriter = new QueryBasedRelationRewriter(may, must, candidateMay, candidateMust); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | private void createFallbackInterpretation() { | ||
319 | if (interpretationFactory == null) { | ||
320 | interpretationFactory = new QueryBasedRelationInterpretationFactory(may, must, candidateMay, candidateMust); | ||
321 | } | ||
322 | } | ||
323 | |||
324 | private void createFallbackRefiner() { | ||
325 | if (interpretationRefiner == null && storageSymbol != null && storageSymbol.valueType() == TruthValue.class) { | ||
326 | // We checked in the condition that this is safe. | ||
327 | @SuppressWarnings("unchecked") | ||
328 | var typedStorageSymbol = (Symbol<TruthValue>) storageSymbol; | ||
329 | interpretationRefiner = ConcreteSymbolRefiner.of(typedStorageSymbol); | ||
330 | } | ||
331 | } | ||
332 | |||
333 | private void createFallbackExclude() { | ||
334 | if (excludeWasSet) { | ||
335 | return; | ||
336 | } | ||
337 | var excludeQuery = createQuery("exclude", (builder, parameters) -> { | ||
338 | var literals = new ArrayList<Literal>(parameters.length + 2); | ||
339 | literals.add(PartialLiterals.must(partialRelation.call(parameters))); | ||
340 | literals.add(not(PartialLiterals.may(partialRelation.call(parameters)))); | ||
341 | for (var parameter : parameters) { | ||
342 | literals.add(PartialLiterals.must(ReasoningAdapter.EXISTS_SYMBOL.call(parameter))); | ||
343 | } | ||
344 | builder.clause(literals); | ||
345 | }); | ||
346 | exclude = Criteria.whenHasMatch(excludeQuery); | ||
347 | } | ||
348 | |||
349 | private void createFallbackObjective() { | ||
350 | if (acceptWasSet && objectiveWasSet) { | ||
351 | return; | ||
352 | } | ||
353 | var invalidCandidate = createQuery("invalidCandidate", (builder, parameters) -> builder | ||
354 | .clause( | ||
355 | PartialLiterals.candidateMust(partialRelation.call(parameters)), | ||
356 | not(PartialLiterals.candidateMay(partialRelation.call(parameters))) | ||
357 | ) | ||
358 | .clause( | ||
359 | PartialLiterals.candidateMust(partialRelation.call(parameters)), | ||
360 | not(PartialLiterals.may(partialRelation.call(parameters))) | ||
361 | ) | ||
362 | .clause( | ||
363 | PartialLiterals.must(partialRelation.call(parameters)), | ||
364 | not(PartialLiterals.candidateMay(partialRelation.call(parameters))) | ||
365 | )); | ||
366 | var reject = createQuery("reject", (builder, parameters) -> { | ||
367 | var literals = new ArrayList<Literal>(parameters.length + 1); | ||
368 | literals.add(invalidCandidate.call(parameters)); | ||
369 | for (var parameter : parameters) { | ||
370 | literals.add(PartialLiterals.candidateMust(ReasoningAdapter.EXISTS_SYMBOL.call(parameter))); | ||
371 | } | ||
372 | builder.clause(literals); | ||
373 | }); | ||
374 | if (!acceptWasSet) { | ||
375 | accept = Criteria.whenNoMatch(reject); | ||
376 | } | ||
377 | if (!objectiveWasSet) { | ||
378 | objective = Objectives.count(reject); | ||
379 | } | ||
380 | } | ||
381 | |||
382 | public PartialRelationRewriter getRewriter() { | ||
383 | checkConfigured(); | ||
384 | return rewriter; | ||
385 | } | ||
386 | |||
387 | public static PartialRelationTranslator of(PartialRelation relation) { | ||
388 | return new PartialRelationTranslator(relation); | ||
389 | } | ||
390 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java new file mode 100644 index 00000000..6cdb287d --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java | |||
@@ -0,0 +1,212 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import org.jetbrains.annotations.Nullable; | ||
9 | import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder; | ||
10 | import tools.refinery.store.dse.transition.Rule; | ||
11 | import tools.refinery.store.dse.transition.objectives.Criterion; | ||
12 | import tools.refinery.store.dse.transition.objectives.Objective; | ||
13 | import tools.refinery.store.model.ModelStoreBuilder; | ||
14 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
15 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
16 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
17 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
18 | import tools.refinery.store.reasoning.refinement.StorageRefiner; | ||
19 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
20 | import tools.refinery.store.reasoning.seed.SeedInitializer; | ||
21 | import tools.refinery.store.representation.AnySymbol; | ||
22 | import tools.refinery.store.representation.Symbol; | ||
23 | |||
24 | import java.util.ArrayList; | ||
25 | import java.util.List; | ||
26 | |||
27 | @SuppressWarnings("UnusedReturnValue") | ||
28 | public abstract sealed class PartialSymbolTranslator<A, C> implements AnyPartialSymbolTranslator | ||
29 | permits PartialRelationTranslator { | ||
30 | private final PartialSymbol<A, C> partialSymbol; | ||
31 | private boolean configured = false; | ||
32 | protected PartialInterpretationRefiner.Factory<A, C> interpretationRefiner; | ||
33 | protected AnySymbol storageSymbol; | ||
34 | protected StorageRefiner.Factory<?> storageRefiner; | ||
35 | protected PartialInterpretation.Factory<A, C> interpretationFactory; | ||
36 | protected PartialModelInitializer initializer; | ||
37 | protected List<Rule> decisionRules = new ArrayList<>(); | ||
38 | protected boolean acceptWasSet; | ||
39 | protected @Nullable Criterion accept; | ||
40 | protected boolean excludeWasSet; | ||
41 | protected @Nullable Criterion exclude; | ||
42 | protected boolean objectiveWasSet; | ||
43 | protected @Nullable Objective objective; | ||
44 | |||
45 | PartialSymbolTranslator(PartialSymbol<A, C> partialSymbol) { | ||
46 | this.partialSymbol = partialSymbol; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public PartialSymbol<A, C> getPartialSymbol() { | ||
51 | return partialSymbol; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public void apply(ModelStoreBuilder storeBuilder) { | ||
56 | storeBuilder.getAdapter(ReasoningBuilder.class).partialSymbol(this); | ||
57 | } | ||
58 | |||
59 | public boolean isConfigured() { | ||
60 | return configured; | ||
61 | } | ||
62 | |||
63 | protected void checkConfigured() { | ||
64 | if (!configured) { | ||
65 | throw new IllegalStateException("Partial symbol was not configured"); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | protected void checkNotConfigured() { | ||
70 | if (configured) { | ||
71 | throw new IllegalStateException("Partial symbol was already configured"); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | public PartialSymbolTranslator<A, C> symbol(AnySymbol storageSymbol) { | ||
76 | return symbol((Symbol<?>) storageSymbol, null); | ||
77 | } | ||
78 | |||
79 | public <T> PartialSymbolTranslator<A, C> symbol(Symbol<T> storageSymbol, | ||
80 | StorageRefiner.Factory<T> storageRefiner) { | ||
81 | checkNotConfigured(); | ||
82 | if (this.storageSymbol != null) { | ||
83 | throw new IllegalStateException("Representation symbol was already set"); | ||
84 | } | ||
85 | this.storageSymbol = storageSymbol; | ||
86 | this.storageRefiner = storageRefiner; | ||
87 | return this; | ||
88 | } | ||
89 | |||
90 | public PartialSymbolTranslator<A, C> interpretation(PartialInterpretation.Factory<A, C> interpretationFactory) { | ||
91 | checkNotConfigured(); | ||
92 | if (this.interpretationFactory != null) { | ||
93 | throw new IllegalStateException("Interpretation factory was already set"); | ||
94 | } | ||
95 | this.interpretationFactory = interpretationFactory; | ||
96 | return this; | ||
97 | } | ||
98 | |||
99 | public PartialSymbolTranslator<A, C> refiner(PartialInterpretationRefiner.Factory<A, C> interpretationRefiner) { | ||
100 | checkNotConfigured(); | ||
101 | if (this.interpretationRefiner != null) { | ||
102 | throw new IllegalStateException("Interpretation refiner was already set"); | ||
103 | } | ||
104 | this.interpretationRefiner = interpretationRefiner; | ||
105 | return this; | ||
106 | } | ||
107 | |||
108 | public PartialSymbolTranslator<A, C> initializer(PartialModelInitializer initializer) { | ||
109 | checkNotConfigured(); | ||
110 | if (this.initializer != null) { | ||
111 | throw new IllegalStateException("Initializer was already set"); | ||
112 | } | ||
113 | this.initializer = initializer; | ||
114 | return this; | ||
115 | } | ||
116 | |||
117 | public PartialSymbolTranslator<A, C> decision(Rule decisionRule) { | ||
118 | decisionRules.add(decisionRule); | ||
119 | return this; | ||
120 | } | ||
121 | |||
122 | public PartialSymbolTranslator<A, C> accept(@Nullable Criterion acceptanceCriterion) { | ||
123 | if (acceptWasSet) { | ||
124 | throw new IllegalStateException("Accept was already set"); | ||
125 | } | ||
126 | this.accept = acceptanceCriterion; | ||
127 | acceptWasSet = true; | ||
128 | return this; | ||
129 | } | ||
130 | |||
131 | public PartialSymbolTranslator<A, C> exclude(@Nullable Criterion exclusionCriterion) { | ||
132 | if (excludeWasSet) { | ||
133 | throw new IllegalStateException("Exclude was already set"); | ||
134 | } | ||
135 | this.exclude = exclusionCriterion; | ||
136 | excludeWasSet = true; | ||
137 | return this; | ||
138 | } | ||
139 | |||
140 | public PartialSymbolTranslator<A, C> objective(Objective objective) { | ||
141 | if (objectiveWasSet) { | ||
142 | throw new IllegalStateException("Objective was already set"); | ||
143 | } | ||
144 | this.objective = objective; | ||
145 | objectiveWasSet = true; | ||
146 | return this; | ||
147 | } | ||
148 | |||
149 | @Override | ||
150 | public void configure(ModelStoreBuilder storeBuilder) { | ||
151 | checkNotConfigured(); | ||
152 | doConfigure(storeBuilder); | ||
153 | configured = true; | ||
154 | } | ||
155 | |||
156 | protected void doConfigure(ModelStoreBuilder storeBuilder) { | ||
157 | if (interpretationFactory == null) { | ||
158 | throw new IllegalArgumentException("Interpretation factory must be set"); | ||
159 | } | ||
160 | var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class); | ||
161 | if (storageSymbol != null) { | ||
162 | storeBuilder.symbol(storageSymbol); | ||
163 | if (storageRefiner != null) { | ||
164 | registerStorageRefiner(reasoningBuilder, storageRefiner); | ||
165 | } | ||
166 | } | ||
167 | createFallbackInitializer(); | ||
168 | if (initializer != null) { | ||
169 | reasoningBuilder.initializer(initializer); | ||
170 | } | ||
171 | storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class).ifPresent(dseBuilder -> { | ||
172 | dseBuilder.transformations(decisionRules); | ||
173 | if (accept != null) { | ||
174 | dseBuilder.accept(accept); | ||
175 | } | ||
176 | if (exclude != null) { | ||
177 | dseBuilder.exclude(exclude); | ||
178 | } | ||
179 | }); | ||
180 | if (objective != null) { | ||
181 | reasoningBuilder.objective(objective); | ||
182 | } | ||
183 | } | ||
184 | |||
185 | private <T> void registerStorageRefiner(ReasoningBuilder reasoningBuilder, StorageRefiner.Factory<T> factory) { | ||
186 | // The builder only allows setting a well-typed representation refiner. | ||
187 | @SuppressWarnings("unchecked") | ||
188 | var typedStorageSymbol = (Symbol<T>) storageSymbol; | ||
189 | reasoningBuilder.storageRefiner(typedStorageSymbol, factory); | ||
190 | } | ||
191 | |||
192 | private void createFallbackInitializer() { | ||
193 | if (initializer == null && | ||
194 | storageSymbol != null && | ||
195 | storageSymbol.valueType().equals(partialSymbol.abstractDomain().abstractType())) { | ||
196 | // The guard clause makes this safe. | ||
197 | @SuppressWarnings("unchecked") | ||
198 | var typedStorageSymbol = (Symbol<A>) storageSymbol; | ||
199 | initializer = new SeedInitializer<>(typedStorageSymbol, partialSymbol); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | public PartialInterpretation.Factory<A, C> getInterpretationFactory() { | ||
204 | checkConfigured(); | ||
205 | return interpretationFactory; | ||
206 | } | ||
207 | |||
208 | public PartialInterpretationRefiner.Factory<A, C> getInterpretationRefiner() { | ||
209 | checkConfigured(); | ||
210 | return interpretationRefiner; | ||
211 | } | ||
212 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java new file mode 100644 index 00000000..dd956a50 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java | |||
@@ -0,0 +1,12 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | public enum RoundingMode { | ||
9 | NONE, | ||
10 | PREFER_TRUE, | ||
11 | PREFER_FALSE | ||
12 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java deleted file mode 100644 index 4a5a8843..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | import tools.refinery.store.query.literal.CallPolarity; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.literal.Modality; | ||
14 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | |||
17 | import java.util.List; | ||
18 | |||
19 | public interface TranslatedRelation { | ||
20 | PartialRelation getSource(); | ||
21 | |||
22 | void configure(List<Advice> advices); | ||
23 | |||
24 | List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments); | ||
25 | |||
26 | PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model); | ||
27 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java new file mode 100644 index 00000000..edb886ba --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.AnyPartialSymbol; | ||
9 | |||
10 | public class TranslationException extends RuntimeException { | ||
11 | private final transient AnyPartialSymbol partialSymbol; | ||
12 | |||
13 | public TranslationException(AnyPartialSymbol partialSymbol) { | ||
14 | this.partialSymbol = partialSymbol; | ||
15 | } | ||
16 | |||
17 | public TranslationException(AnyPartialSymbol partialSymbol, String message) { | ||
18 | super(message); | ||
19 | this.partialSymbol = partialSymbol; | ||
20 | } | ||
21 | |||
22 | public TranslationException(AnyPartialSymbol partialSymbol, String message, Throwable cause) { | ||
23 | super(message, cause); | ||
24 | this.partialSymbol = partialSymbol; | ||
25 | } | ||
26 | |||
27 | public TranslationException(AnyPartialSymbol partialSymbol, Throwable cause) { | ||
28 | super(cause); | ||
29 | this.partialSymbol = partialSymbol; | ||
30 | } | ||
31 | |||
32 | public AnyPartialSymbol getPartialSymbol() { | ||
33 | return partialSymbol; | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java deleted file mode 100644 index 6e44a7d7..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
10 | |||
11 | import java.util.Collection; | ||
12 | |||
13 | public abstract class TranslationUnit { | ||
14 | private ReasoningBuilder reasoningBuilder; | ||
15 | |||
16 | protected ReasoningBuilder getReasoningBuilder() { | ||
17 | return reasoningBuilder; | ||
18 | } | ||
19 | |||
20 | public void setPartialInterpretationBuilder(ReasoningBuilder reasoningBuilder) { | ||
21 | this.reasoningBuilder = reasoningBuilder; | ||
22 | configureReasoningBuilder(); | ||
23 | } | ||
24 | |||
25 | protected void configureReasoningBuilder() { | ||
26 | // Nothing to configure by default. | ||
27 | } | ||
28 | |||
29 | public abstract Collection<TranslatedRelation> getTranslatedRelations(); | ||
30 | |||
31 | public abstract void initializeModel(Model model, int nodeCount); | ||
32 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java deleted file mode 100644 index 2a151aa2..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java +++ /dev/null | |||
@@ -1,93 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.map.Cursor; | ||
9 | import tools.refinery.store.model.Interpretation; | ||
10 | import tools.refinery.store.query.resultset.ResultSet; | ||
11 | import tools.refinery.store.reasoning.MergeResult; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
14 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | public class BaseDecisionInterpretation implements PartialInterpretation<TruthValue, Boolean> { | ||
19 | private final ReasoningAdapter reasoningAdapter; | ||
20 | private PartialRelation partialRelation; | ||
21 | private final ResultSet<Boolean> mustResultSet; | ||
22 | private final ResultSet<Boolean> mayResultSet; | ||
23 | private final ResultSet<Boolean> errorResultSet; | ||
24 | private final ResultSet<Boolean> currentResultSet; | ||
25 | private final Interpretation<TruthValue> interpretation; | ||
26 | |||
27 | public BaseDecisionInterpretation(ReasoningAdapter reasoningAdapter, ResultSet<Boolean> mustResultSet, | ||
28 | ResultSet<Boolean> mayResultSet, ResultSet<Boolean> errorResultSet, | ||
29 | ResultSet<Boolean> currentResultSet, Interpretation<TruthValue> interpretation) { | ||
30 | this.reasoningAdapter = reasoningAdapter; | ||
31 | this.mustResultSet = mustResultSet; | ||
32 | this.mayResultSet = mayResultSet; | ||
33 | this.errorResultSet = errorResultSet; | ||
34 | this.currentResultSet = currentResultSet; | ||
35 | this.interpretation = interpretation; | ||
36 | } | ||
37 | |||
38 | @Override | ||
39 | public ReasoningAdapter getAdapter() { | ||
40 | return reasoningAdapter; | ||
41 | } | ||
42 | |||
43 | @Override | ||
44 | public int countUnfinished() { | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | @Override | ||
49 | public int countErrors() { | ||
50 | return errorResultSet.size(); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public PartialRelation getPartialSymbol() { | ||
55 | return partialRelation; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public TruthValue get(Tuple key) { | ||
60 | return null; | ||
61 | } | ||
62 | |||
63 | @Override | ||
64 | public Cursor<Tuple, TruthValue> getAll() { | ||
65 | return null; | ||
66 | } | ||
67 | |||
68 | @Override | ||
69 | public MergeResult merge(Tuple key, TruthValue value) { | ||
70 | TruthValue newValue; | ||
71 | switch (value) { | ||
72 | case UNKNOWN -> { | ||
73 | return MergeResult.UNCHANGED; | ||
74 | } | ||
75 | case TRUE -> newValue = mayResultSet.get(key) ? TruthValue.TRUE : TruthValue.ERROR; | ||
76 | case FALSE -> newValue = mustResultSet.get(key) ? TruthValue.ERROR : TruthValue.FALSE; | ||
77 | case ERROR -> newValue = TruthValue.ERROR; | ||
78 | default -> throw new IllegalArgumentException("Unknown truth value: " + value); | ||
79 | } | ||
80 | var oldValue = interpretation.put(key, newValue); | ||
81 | return oldValue == TruthValue.ERROR ? MergeResult.UNCHANGED : MergeResult.REFINED; | ||
82 | } | ||
83 | |||
84 | @Override | ||
85 | public Boolean getConcrete(Tuple key) { | ||
86 | return currentResultSet.get(key); | ||
87 | } | ||
88 | |||
89 | @Override | ||
90 | public Cursor<Tuple, Boolean> getAllConcrete() { | ||
91 | return null; | ||
92 | } | ||
93 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java deleted file mode 100644 index a1e4b816..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java +++ /dev/null | |||
@@ -1,49 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.seed.Seed; | ||
11 | import tools.refinery.store.reasoning.seed.UniformSeed; | ||
12 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
13 | import tools.refinery.store.reasoning.translator.TranslationUnit; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | |||
17 | import java.util.Collection; | ||
18 | import java.util.List; | ||
19 | |||
20 | public class BaseDecisionTranslationUnit extends TranslationUnit { | ||
21 | private final PartialRelation partialRelation; | ||
22 | private final Seed<TruthValue> seed; | ||
23 | private final Symbol<TruthValue> symbol; | ||
24 | |||
25 | public BaseDecisionTranslationUnit(PartialRelation partialRelation, Seed<TruthValue> seed) { | ||
26 | if (seed.arity() != partialRelation.arity()) { | ||
27 | throw new IllegalArgumentException("Expected seed with arity %d for %s, got arity %s" | ||
28 | .formatted(partialRelation.arity(), partialRelation, seed.arity())); | ||
29 | } | ||
30 | this.partialRelation = partialRelation; | ||
31 | this.seed = seed; | ||
32 | symbol = Symbol.of(partialRelation.name(), partialRelation.arity(), TruthValue.class, TruthValue.UNKNOWN); | ||
33 | } | ||
34 | |||
35 | public BaseDecisionTranslationUnit(PartialRelation partialRelation) { | ||
36 | this(partialRelation, new UniformSeed<>(partialRelation.arity(), TruthValue.UNKNOWN)); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public Collection<TranslatedRelation> getTranslatedRelations() { | ||
41 | return List.of(new TranslatedBaseDecision(getReasoningBuilder(), partialRelation, symbol)); | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public void initializeModel(Model model, int nodeCount) { | ||
46 | var interpretation = model.getInterpretation(symbol); | ||
47 | interpretation.putAll(seed.getCursor(TruthValue.UNKNOWN, nodeCount)); | ||
48 | } | ||
49 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java deleted file mode 100644 index 4782eb46..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.base; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.query.term.Variable; | ||
10 | import tools.refinery.store.query.literal.CallPolarity; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.reasoning.PartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
14 | import tools.refinery.store.reasoning.literal.Modality; | ||
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
16 | import tools.refinery.store.reasoning.translator.Advice; | ||
17 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
18 | import tools.refinery.store.representation.Symbol; | ||
19 | import tools.refinery.store.representation.TruthValue; | ||
20 | |||
21 | import java.util.List; | ||
22 | |||
23 | class TranslatedBaseDecision implements TranslatedRelation { | ||
24 | private final ReasoningBuilder reasoningBuilder; | ||
25 | private final PartialRelation partialRelation; | ||
26 | private final Symbol<TruthValue> symbol; | ||
27 | |||
28 | public TranslatedBaseDecision(ReasoningBuilder reasoningBuilder, PartialRelation partialRelation, | ||
29 | Symbol<TruthValue> symbol) { | ||
30 | this.reasoningBuilder = reasoningBuilder; | ||
31 | this.partialRelation = partialRelation; | ||
32 | this.symbol = symbol; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public PartialRelation getSource() { | ||
37 | return partialRelation; | ||
38 | } | ||
39 | |||
40 | @Override | ||
41 | public void configure(List<Advice> advices) { | ||
42 | |||
43 | } | ||
44 | |||
45 | @Override | ||
46 | public List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments) { | ||
47 | return null; | ||
48 | } | ||
49 | |||
50 | @Override | ||
51 | public PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model) { | ||
52 | return null; | ||
53 | } | ||
54 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java new file mode 100644 index 00000000..61037be3 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java | |||
@@ -0,0 +1,255 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder; | ||
9 | import tools.refinery.store.dse.transition.Rule; | ||
10 | import tools.refinery.store.model.ModelStoreBuilder; | ||
11 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
14 | import tools.refinery.store.query.literal.Connectivity; | ||
15 | import tools.refinery.store.query.literal.Literal; | ||
16 | import tools.refinery.store.query.literal.RepresentativeElectionLiteral; | ||
17 | import tools.refinery.store.query.term.Variable; | ||
18 | import tools.refinery.store.query.view.AnySymbolView; | ||
19 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
20 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
21 | import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral; | ||
22 | import tools.refinery.store.reasoning.literal.Modality; | ||
23 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
24 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
25 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
26 | import tools.refinery.store.reasoning.translator.RoundingMode; | ||
27 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
28 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
29 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | ||
30 | import tools.refinery.store.representation.Symbol; | ||
31 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
32 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
33 | |||
34 | import java.util.ArrayList; | ||
35 | import java.util.List; | ||
36 | import java.util.Map; | ||
37 | |||
38 | import static tools.refinery.store.query.literal.Literals.check; | ||
39 | import static tools.refinery.store.query.literal.Literals.not; | ||
40 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
41 | import static tools.refinery.store.query.term.int_.IntTerms.less; | ||
42 | import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL; | ||
43 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | ||
44 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.focus; | ||
45 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; | ||
46 | |||
47 | public class ContainmentHierarchyTranslator implements ModelStoreConfiguration { | ||
48 | public static final PartialRelation CONTAINED_SYMBOL = new PartialRelation("contained", 1); | ||
49 | public static final PartialRelation INVALID_CONTAINER = new PartialRelation("invalidContainer", | ||
50 | 1); | ||
51 | public static final PartialRelation CONTAINS_SYMBOL = new PartialRelation("contains", 2); | ||
52 | |||
53 | private final Symbol<InferredContainment> containsStorage = Symbol.of("CONTAINS", 2, InferredContainment.class, | ||
54 | InferredContainment.UNKNOWN); | ||
55 | private final AnySymbolView forbiddenContainsView = new ForbiddenContainsView(containsStorage); | ||
56 | private final RelationalQuery containsMayNewTargetHelper; | ||
57 | private final RelationalQuery containsMayExistingHelper; | ||
58 | private final RelationalQuery weakComponents; | ||
59 | private final RelationalQuery strongComponents; | ||
60 | private final Map<PartialRelation, ContainmentInfo> containmentInfoMap; | ||
61 | |||
62 | public ContainmentHierarchyTranslator(Map<PartialRelation, ContainmentInfo> containmentInfoMap) { | ||
63 | this.containmentInfoMap = containmentInfoMap; | ||
64 | |||
65 | var name = CONTAINS_SYMBOL.name(); | ||
66 | |||
67 | containsMayNewTargetHelper = Query.of(name + "#mayNewTargetHelper", (builder, child) -> builder | ||
68 | .clause(Integer.class, existingContainers -> List.of( | ||
69 | may(CONTAINED_SYMBOL.call(child)), | ||
70 | new CountLowerBoundLiteral(existingContainers, CONTAINS_SYMBOL, List.of(Variable.of(), child)), | ||
71 | check(less(existingContainers, constant(1))) | ||
72 | ))); | ||
73 | |||
74 | containsMayExistingHelper = Query.of(name + "#mayExistingHelper", (builder, parent, child) -> builder | ||
75 | .clause(Integer.class, existingContainers -> List.of( | ||
76 | must(CONTAINS_SYMBOL.call(parent, child)), | ||
77 | not(forbiddenContainsView.call(parent, child)) | ||
78 | // Violation of monotonicity: | ||
79 | // Containment edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
80 | // {@code invalidNumberOfContainers} error pattern will already mark the node as invalid. | ||
81 | ))); | ||
82 | |||
83 | var mustExistBothContains = Query.of(name + "#mustExistBoth", (builder, parent, child) -> builder.clause( | ||
84 | must(CONTAINS_SYMBOL.call(parent, child)), | ||
85 | must(EXISTS_SYMBOL.call(parent)), | ||
86 | must(EXISTS_SYMBOL.call(child)) | ||
87 | )); | ||
88 | |||
89 | weakComponents = Query.of(name + "#weakComponents", (builder, node, representative) -> builder.clause( | ||
90 | new RepresentativeElectionLiteral(Connectivity.WEAK, mustExistBothContains.getDnf(), node, | ||
91 | representative) | ||
92 | )); | ||
93 | |||
94 | strongComponents = Query.of(name + "#strongComponents", (builder, node, representative) -> builder.clause( | ||
95 | new RepresentativeElectionLiteral(Connectivity.STRONG, mustExistBothContains.getDnf(), node, | ||
96 | representative) | ||
97 | )); | ||
98 | } | ||
99 | |||
100 | @Override | ||
101 | public void apply(ModelStoreBuilder storeBuilder) { | ||
102 | storeBuilder.symbol(containsStorage); | ||
103 | translateContains(storeBuilder); | ||
104 | translateInvalidContainer(storeBuilder); | ||
105 | for (var entry : containmentInfoMap.entrySet()) { | ||
106 | var linkType = entry.getKey(); | ||
107 | var info = entry.getValue(); | ||
108 | translateContainmentLinkType(storeBuilder, linkType, info); | ||
109 | translateInvalidMultiplicity(storeBuilder, linkType, info); | ||
110 | } | ||
111 | translateFocusNotContained(storeBuilder); | ||
112 | } | ||
113 | |||
114 | private void translateContainmentLinkType(ModelStoreBuilder storeBuilder, PartialRelation linkType, | ||
115 | ContainmentInfo info) { | ||
116 | var name = linkType.name(); | ||
117 | var sourceType = info.sourceType(); | ||
118 | var targetType = info.targetType(); | ||
119 | var upperCardinality = info.multiplicity().multiplicity().upperBound(); | ||
120 | |||
121 | var mayNewSourceHelper = Query.of(name + "#mayNewSourceHelper", (builder, parent) -> { | ||
122 | var literals = new ArrayList<Literal>(); | ||
123 | literals.add(may(sourceType.call(parent))); | ||
124 | if (upperCardinality instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
125 | var existingCount = Variable.of("existingCount", Integer.class); | ||
126 | literals.add(new CountLowerBoundLiteral(existingCount, linkType, List.of(parent, Variable.of()))); | ||
127 | literals.add(check(less(existingCount, constant(finiteUpperCardinality.finiteUpperBound())))); | ||
128 | } | ||
129 | builder.clause(literals); | ||
130 | }); | ||
131 | |||
132 | var mayNewTargetHelper = Query.of(name + "#mayNewTargetHelper", (builder, child) -> builder.clause( | ||
133 | containsMayNewTargetHelper.call(child), | ||
134 | may(targetType.call(child)) | ||
135 | )); | ||
136 | |||
137 | var forbiddenLinkView = new ForbiddenContainmentLinkView(containsStorage, linkType); | ||
138 | |||
139 | var mayNewHelper = Query.of(name + "#mayNewHelper", (builder, parent, child) -> builder.clause( | ||
140 | mayNewSourceHelper.call(parent), | ||
141 | mayNewTargetHelper.call(child), | ||
142 | not(must(CONTAINS_SYMBOL.call(parent, child))), | ||
143 | not(forbiddenLinkView.call(parent, child)) | ||
144 | )); | ||
145 | |||
146 | var mayExistingHelper = Query.of(name + "#mayExistingHelper", (builder, parent, child) -> builder.clause( | ||
147 | must(linkType.call(parent, child)), | ||
148 | containsMayExistingHelper.call(parent, child), | ||
149 | may(sourceType.call(parent)), | ||
150 | may(targetType.call(child)), | ||
151 | not(forbiddenLinkView.call(parent, child)) | ||
152 | // Violation of monotonicity: | ||
153 | // Containment edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
154 | // {@code invalidNumberOfContainers} error pattern will already mark the node as invalid. | ||
155 | )); | ||
156 | |||
157 | var may = Query.of(name + "#may", (builder, parent, child) -> builder | ||
158 | .clause( | ||
159 | mayNewHelper.call(parent, child), | ||
160 | not(weakComponents.call(parent, Variable.of())) | ||
161 | ) | ||
162 | .clause(representative -> List.of( | ||
163 | mayNewHelper.call(parent, child), | ||
164 | weakComponents.call(parent, representative), | ||
165 | // Violation of para-consistency: | ||
166 | // If there is a surely existing node with at least two containers, its (transitive) containers | ||
167 | // will end up in the same weakly connected component, and we will spuriously mark the | ||
168 | // containment edge between the (transitive) containers as {@code FALSE}. However, such | ||
169 | // models can never be finished. | ||
170 | // | ||
171 | // Violation of monotonicity: | ||
172 | // if the a {@code TRUE} value is added to the representation in the previous situation, | ||
173 | // we'll check strongly connected components instead of weakly connected ones. Therefore, the | ||
174 | // view for the partial symbol will change from {@code FALSE} to {@code TRUE}. This doesn't | ||
175 | // affect the overall inconsistency of the partial model (due to the surely existing node | ||
176 | // with multiple containers). | ||
177 | not(weakComponents.call(child, representative)) | ||
178 | )) | ||
179 | .clause( | ||
180 | mayExistingHelper.call(parent, child), | ||
181 | not(strongComponents.call(parent, Variable.of())) | ||
182 | ) | ||
183 | .clause(representative -> List.of( | ||
184 | mayExistingHelper.call(parent, child), | ||
185 | strongComponents.call(parent, representative), | ||
186 | not(strongComponents.call(child, representative)) | ||
187 | ))); | ||
188 | |||
189 | storeBuilder.with(PartialRelationTranslator.of(linkType) | ||
190 | .may(may) | ||
191 | .must(Query.of(name + "#must", (builder, parent, child) -> builder.clause( | ||
192 | new MustContainmentLinkView(containsStorage, linkType).call(parent, child) | ||
193 | ))) | ||
194 | .roundingMode(RoundingMode.PREFER_FALSE) | ||
195 | .refiner(ContainmentLinkRefiner.of(linkType, containsStorage, info.sourceType(), info.targetType())) | ||
196 | .initializer(new RefinementBasedInitializer<>(linkType)) | ||
197 | .decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
198 | .clause( | ||
199 | may(linkType.call(source, target)), | ||
200 | not(candidateMust(linkType.call(source, target))), | ||
201 | not(MultiObjectTranslator.MULTI_VIEW.call(source)) | ||
202 | ) | ||
203 | .action(focusedTarget -> List.of( | ||
204 | focus(target, focusedTarget), | ||
205 | add(linkType, source, focusedTarget) | ||
206 | ))))); | ||
207 | } | ||
208 | |||
209 | private void translateInvalidMultiplicity(ModelStoreBuilder storeBuilder, PartialRelation linkType, | ||
210 | ContainmentInfo info) { | ||
211 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(info.sourceType(), linkType, false, | ||
212 | info.multiplicity())); | ||
213 | } | ||
214 | |||
215 | private void translateContains(ModelStoreBuilder storeBuilder) { | ||
216 | var name = CONTAINS_SYMBOL.name(); | ||
217 | var mustName = DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL); | ||
218 | |||
219 | storeBuilder.with(PartialRelationTranslator.of(CONTAINS_SYMBOL) | ||
220 | .query(Query.of(name, (builder, parent, child) -> { | ||
221 | for (var linkType : containmentInfoMap.keySet()) { | ||
222 | builder.clause(linkType.call(parent, child)); | ||
223 | } | ||
224 | })) | ||
225 | .must(Query.of(mustName, (builder, parent, child) -> builder.clause( | ||
226 | new MustContainsView(containsStorage).call(parent, child) | ||
227 | )))); | ||
228 | } | ||
229 | |||
230 | private void translateInvalidContainer(ModelStoreBuilder storeBuilder) { | ||
231 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(CONTAINED_SYMBOL, CONTAINS_SYMBOL, true, | ||
232 | ConstrainedMultiplicity.of(CardinalityIntervals.ONE, INVALID_CONTAINER))); | ||
233 | } | ||
234 | |||
235 | private void translateFocusNotContained(ModelStoreBuilder storeBuilder) { | ||
236 | var dseBuilderOption = storeBuilder.tryGetAdapter(DesignSpaceExplorationBuilder.class); | ||
237 | if (dseBuilderOption.isEmpty()) { | ||
238 | return; | ||
239 | } | ||
240 | var dseBuilder = dseBuilderOption.get(); | ||
241 | dseBuilder.transformation(Rule.of("NOT_CONTAINED", (builder, multi) -> builder | ||
242 | .clause( | ||
243 | MultiObjectTranslator.MULTI_VIEW.call(multi), | ||
244 | not(may(CONTAINED_SYMBOL.call(multi))) | ||
245 | ) | ||
246 | .clause(container -> List.of( | ||
247 | MultiObjectTranslator.MULTI_VIEW.call(multi), | ||
248 | must(CONTAINS_SYMBOL.call(container, multi)), | ||
249 | not(MultiObjectTranslator.MULTI_VIEW.call(container)) | ||
250 | )) | ||
251 | .action( | ||
252 | focus(multi, Variable.of()) | ||
253 | ))); | ||
254 | } | ||
255 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java new file mode 100644 index 00000000..e3457fa7 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java | |||
@@ -0,0 +1,24 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
11 | |||
12 | public record ContainmentInfo(PartialRelation sourceType, Multiplicity multiplicity, | ||
13 | PartialRelation targetType) { | ||
14 | public ContainmentInfo { | ||
15 | if (sourceType.arity() != 1) { | ||
16 | throw new TranslationException(sourceType, "Expected source type %s to be of arity 1, got %d instead" | ||
17 | .formatted(sourceType, sourceType.arity())); | ||
18 | } | ||
19 | if (targetType.arity() != 1) { | ||
20 | throw new TranslationException(targetType, "Expected target type %s to be of arity 1, got %d instead" | ||
21 | .formatted(targetType, targetType.arity())); | ||
22 | } | ||
23 | } | ||
24 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java new file mode 100644 index 00000000..497ed98f --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java | |||
@@ -0,0 +1,128 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
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.refinement.PartialInterpretationRefiner; | ||
12 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
13 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
14 | import tools.refinery.store.representation.Symbol; | ||
15 | import tools.refinery.store.representation.TruthValue; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | import java.util.ArrayList; | ||
19 | import java.util.Set; | ||
20 | |||
21 | class ContainmentLinkRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> { | ||
22 | private final Factory factory; | ||
23 | private final Interpretation<InferredContainment> interpretation; | ||
24 | private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner; | ||
25 | private final PartialInterpretationRefiner<TruthValue, Boolean> targetRefiner; | ||
26 | |||
27 | public ContainmentLinkRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
28 | Factory factory) { | ||
29 | super(adapter, partialSymbol); | ||
30 | this.factory = factory; | ||
31 | interpretation = adapter.getModel().getInterpretation(factory.symbol); | ||
32 | sourceRefiner = adapter.getRefiner(factory.sourceType); | ||
33 | targetRefiner = adapter.getRefiner(factory.targetType); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public boolean merge(Tuple key, TruthValue value) { | ||
38 | var oldValue = interpretation.get(key); | ||
39 | var newValue = mergeLink(oldValue, value); | ||
40 | if (oldValue != newValue) { | ||
41 | interpretation.put(key, newValue); | ||
42 | } | ||
43 | if (value.must()) { | ||
44 | return sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) && | ||
45 | targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE); | ||
46 | } | ||
47 | return true; | ||
48 | } | ||
49 | |||
50 | public InferredContainment mergeLink(InferredContainment oldValue, TruthValue toMerge) { | ||
51 | return switch (toMerge) { | ||
52 | case UNKNOWN -> oldValue; | ||
53 | case TRUE -> mustLink(oldValue); | ||
54 | case FALSE -> forbidLink(oldValue); | ||
55 | case ERROR -> errorLink(oldValue); | ||
56 | }; | ||
57 | } | ||
58 | |||
59 | public InferredContainment mustLink(InferredContainment oldValue) { | ||
60 | var mustLinks = oldValue.mustLinks(); | ||
61 | if (oldValue.contains().may() && mustLinks.isEmpty() && oldValue.forbiddenLinks().isEmpty()) { | ||
62 | return factory.trueLink; | ||
63 | } | ||
64 | if (mustLinks.contains(factory.linkType)) { | ||
65 | return oldValue; | ||
66 | } | ||
67 | return new InferredContainment(oldValue.contains().merge(TruthValue.TRUE), | ||
68 | addToSet(mustLinks, factory.linkType), oldValue.forbiddenLinks()); | ||
69 | } | ||
70 | |||
71 | public InferredContainment forbidLink(InferredContainment oldValue) { | ||
72 | var forbiddenLinks = oldValue.forbiddenLinks(); | ||
73 | if (oldValue.contains() == TruthValue.UNKNOWN && oldValue.mustLinks().isEmpty() && forbiddenLinks.isEmpty()) { | ||
74 | return factory.falseLinkUnknownContains; | ||
75 | } | ||
76 | if (forbiddenLinks.contains(factory.linkType)) { | ||
77 | return oldValue; | ||
78 | } | ||
79 | return new InferredContainment(oldValue.contains(), oldValue.mustLinks(), | ||
80 | addToSet(forbiddenLinks, factory.linkType)); | ||
81 | } | ||
82 | |||
83 | public InferredContainment errorLink(InferredContainment oldValue) { | ||
84 | return new InferredContainment(TruthValue.ERROR, addToSet(oldValue.mustLinks(), factory.linkType), | ||
85 | addToSet(oldValue.forbiddenLinks(), factory.linkType)); | ||
86 | } | ||
87 | |||
88 | private static Set<PartialRelation> addToSet(Set<PartialRelation> oldSet, PartialRelation linkType) { | ||
89 | if (oldSet.isEmpty()) { | ||
90 | return Set.of(linkType); | ||
91 | } | ||
92 | var newElements = new ArrayList<PartialRelation>(oldSet.size() + 1); | ||
93 | newElements.addAll(oldSet); | ||
94 | newElements.add(linkType); | ||
95 | return Set.copyOf(newElements); | ||
96 | } | ||
97 | |||
98 | public static PartialInterpretationRefiner.Factory<TruthValue, Boolean> of( | ||
99 | PartialRelation linkType, Symbol<InferredContainment> symbol, PartialRelation sourceType, | ||
100 | PartialRelation targetType) { | ||
101 | return new Factory(linkType, symbol, sourceType, targetType); | ||
102 | } | ||
103 | |||
104 | private static class Factory implements PartialInterpretationRefiner.Factory<TruthValue, Boolean> { | ||
105 | public final PartialRelation linkType; | ||
106 | public final Symbol<InferredContainment> symbol; | ||
107 | public final PartialRelation targetType; | ||
108 | public final PartialRelation sourceType; | ||
109 | public final InferredContainment trueLink; | ||
110 | public final InferredContainment falseLinkUnknownContains; | ||
111 | |||
112 | public Factory(PartialRelation linkType, Symbol<InferredContainment> symbol, PartialRelation sourceType, | ||
113 | PartialRelation targetType) { | ||
114 | this.linkType = linkType; | ||
115 | this.symbol = symbol; | ||
116 | this.sourceType = sourceType; | ||
117 | this.targetType = targetType; | ||
118 | trueLink = new InferredContainment(TruthValue.TRUE, Set.of(linkType), Set.of()); | ||
119 | falseLinkUnknownContains = new InferredContainment(TruthValue.FALSE, Set.of(), Set.of(linkType)); | ||
120 | } | ||
121 | |||
122 | @Override | ||
123 | public PartialInterpretationRefiner<TruthValue, Boolean> create( | ||
124 | ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol) { | ||
125 | return new ContainmentLinkRefiner(adapter, partialSymbol, this); | ||
126 | } | ||
127 | } | ||
128 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java new file mode 100644 index 00000000..cf0e2971 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class ForbiddenContainmentLinkView extends InferredContainmentLinkView { | ||
13 | public ForbiddenContainmentLinkView(Symbol<InferredContainment> symbol, PartialRelation link) { | ||
14 | super(symbol, "must", link); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredContainment value) { | ||
19 | return value.forbiddenLinks().contains(link); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java new file mode 100644 index 00000000..efe674f1 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.query.view.TuplePreservingView; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class ForbiddenContainsView extends TuplePreservingView<InferredContainment> { | ||
13 | public ForbiddenContainsView(Symbol<InferredContainment> symbol) { | ||
14 | super(symbol, "contains#forbidden"); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredContainment value) { | ||
19 | return !value.contains().may(); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java new file mode 100644 index 00000000..8df23d9a --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java | |||
@@ -0,0 +1,77 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | |||
11 | import java.util.Objects; | ||
12 | import java.util.Set; | ||
13 | |||
14 | final class InferredContainment { | ||
15 | public static final InferredContainment UNKNOWN = new InferredContainment( | ||
16 | TruthValue.UNKNOWN, Set.of(), Set.of()); | ||
17 | private final TruthValue contains; | ||
18 | private final Set<PartialRelation> mustLinks; | ||
19 | private final Set<PartialRelation> forbiddenLinks; | ||
20 | private final int hashCode; | ||
21 | |||
22 | public InferredContainment(TruthValue contains, Set<PartialRelation> mustLinks, | ||
23 | Set<PartialRelation> forbiddenLinks) { | ||
24 | this.contains = adjustContains(contains, mustLinks, forbiddenLinks); | ||
25 | this.mustLinks = mustLinks; | ||
26 | this.forbiddenLinks = forbiddenLinks; | ||
27 | hashCode = Objects.hash(contains, mustLinks, forbiddenLinks); | ||
28 | } | ||
29 | |||
30 | private static TruthValue adjustContains(TruthValue contains, Set<PartialRelation> mustLinks, | ||
31 | Set<PartialRelation> forbiddenLinks) { | ||
32 | var result = contains; | ||
33 | if (!mustLinks.isEmpty()) { | ||
34 | result = result.merge(TruthValue.TRUE); | ||
35 | } | ||
36 | boolean hasErrorLink = mustLinks.stream().anyMatch(forbiddenLinks::contains); | ||
37 | if (mustLinks.size() >= 2 || hasErrorLink) { | ||
38 | result = result.merge(TruthValue.ERROR); | ||
39 | } | ||
40 | return result; | ||
41 | } | ||
42 | |||
43 | public TruthValue contains() { | ||
44 | return contains; | ||
45 | } | ||
46 | |||
47 | public Set<PartialRelation> mustLinks() { | ||
48 | return mustLinks; | ||
49 | } | ||
50 | |||
51 | public Set<PartialRelation> forbiddenLinks() { | ||
52 | return forbiddenLinks; | ||
53 | } | ||
54 | |||
55 | @Override | ||
56 | public boolean equals(Object obj) { | ||
57 | if (obj == this) return true; | ||
58 | if (obj == null || obj.getClass() != this.getClass()) return false; | ||
59 | var that = (InferredContainment) obj; | ||
60 | return Objects.equals(this.contains, that.contains) && | ||
61 | Objects.equals(this.mustLinks, that.mustLinks) && | ||
62 | Objects.equals(this.forbiddenLinks, that.forbiddenLinks); | ||
63 | } | ||
64 | |||
65 | @Override | ||
66 | public int hashCode() { | ||
67 | return hashCode; | ||
68 | } | ||
69 | |||
70 | @Override | ||
71 | public String toString() { | ||
72 | return "InferredContainment[" + | ||
73 | "contains=" + contains + ", " + | ||
74 | "mustLinks=" + mustLinks + ", " + | ||
75 | "forbiddenLinks=" + forbiddenLinks + ']'; | ||
76 | } | ||
77 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java new file mode 100644 index 00000000..d187ad91 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java | |||
@@ -0,0 +1,35 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.query.view.TuplePreservingView; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.representation.Symbol; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | abstract class InferredContainmentLinkView extends TuplePreservingView<InferredContainment> { | ||
15 | protected final PartialRelation link; | ||
16 | |||
17 | protected InferredContainmentLinkView(Symbol<InferredContainment> symbol, String name, PartialRelation link) { | ||
18 | super(symbol, link.name() + "#" + name); | ||
19 | this.link = link; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | public boolean equals(Object o) { | ||
24 | if (this == o) return true; | ||
25 | if (o == null || getClass() != o.getClass()) return false; | ||
26 | if (!super.equals(o)) return false; | ||
27 | InferredContainmentLinkView that = (InferredContainmentLinkView) o; | ||
28 | return Objects.equals(link, that.link); | ||
29 | } | ||
30 | |||
31 | @Override | ||
32 | public int hashCode() { | ||
33 | return Objects.hash(super.hashCode(), link); | ||
34 | } | ||
35 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java new file mode 100644 index 00000000..474942d6 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class MustContainmentLinkView extends InferredContainmentLinkView { | ||
13 | public MustContainmentLinkView(Symbol<InferredContainment> symbol, PartialRelation link) { | ||
14 | super(symbol, "must", link); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredContainment value) { | ||
19 | return value.mustLinks().contains(link); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java new file mode 100644 index 00000000..6bc62a59 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import tools.refinery.store.query.view.TuplePreservingView; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class MustContainsView extends TuplePreservingView<InferredContainment> { | ||
13 | public MustContainsView(Symbol<InferredContainment> symbol) { | ||
14 | super(symbol, "contains#must"); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredContainment value) { | ||
19 | return value.contains().must(); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java new file mode 100644 index 00000000..c4a2f2b3 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java | |||
@@ -0,0 +1,57 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.query.dnf.Query; | ||
9 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
10 | import tools.refinery.store.query.literal.Literal; | ||
11 | import tools.refinery.store.query.term.NodeVariable; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral; | ||
14 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
15 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
16 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
17 | |||
18 | import java.util.ArrayList; | ||
19 | import java.util.List; | ||
20 | |||
21 | import static tools.refinery.store.query.literal.Literals.check; | ||
22 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
23 | import static tools.refinery.store.query.term.int_.IntTerms.less; | ||
24 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | ||
25 | |||
26 | class CrossReferenceUtils { | ||
27 | private CrossReferenceUtils() { | ||
28 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
29 | } | ||
30 | |||
31 | public static RelationalQuery createMayHelper(PartialRelation linkType, PartialRelation type, | ||
32 | Multiplicity multiplicity, boolean inverse) { | ||
33 | String name; | ||
34 | NodeVariable variable; | ||
35 | List<Variable> arguments; | ||
36 | if (inverse) { | ||
37 | name = "Target"; | ||
38 | variable = Variable.of("target"); | ||
39 | arguments = List.of(Variable.of("source"), variable); | ||
40 | } else { | ||
41 | name = "Source"; | ||
42 | variable = Variable.of("source"); | ||
43 | arguments = List.of(variable, Variable.of("target")); | ||
44 | } | ||
45 | var builder = Query.builder(linkType.name() + "#mayNew" + name); | ||
46 | builder.parameter(variable); | ||
47 | var literals = new ArrayList<Literal>(); | ||
48 | literals.add(may(type.call(variable))); | ||
49 | if (multiplicity.multiplicity().upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
50 | var existingLinks = Variable.of("existingLinks", Integer.class); | ||
51 | literals.add(new CountLowerBoundLiteral(existingLinks, linkType, arguments)); | ||
52 | literals.add(check(less(existingLinks, constant(finiteUpperCardinality.finiteUpperBound())))); | ||
53 | } | ||
54 | builder.clause(literals); | ||
55 | return builder.build(); | ||
56 | } | ||
57 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java new file mode 100644 index 00000000..d8c6a5ea --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
10 | |||
11 | public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, | ||
12 | PartialRelation targetType, Multiplicity targetMultiplicity) { | ||
13 | public boolean isConstrained() { | ||
14 | return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); | ||
15 | } | ||
16 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java new file mode 100644 index 00000000..0700f9f7 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java | |||
@@ -0,0 +1,46 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner; | ||
10 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
12 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | import tools.refinery.store.representation.TruthValue; | ||
15 | import tools.refinery.store.tuple.Tuple; | ||
16 | |||
17 | class DirectedCrossReferenceRefiner extends ConcreteSymbolRefiner<TruthValue, Boolean> { | ||
18 | private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner; | ||
19 | private final PartialInterpretationRefiner<TruthValue, Boolean> targetRefiner; | ||
20 | |||
21 | public DirectedCrossReferenceRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
22 | Symbol<TruthValue> concreteSymbol, PartialRelation sourceType, | ||
23 | PartialRelation targetType) { | ||
24 | super(adapter, partialSymbol, concreteSymbol); | ||
25 | sourceRefiner = adapter.getRefiner(sourceType); | ||
26 | targetRefiner = adapter.getRefiner(targetType); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public boolean merge(Tuple key, TruthValue value) { | ||
31 | if (!super.merge(key, value)) { | ||
32 | return false; | ||
33 | } | ||
34 | if (value.must()) { | ||
35 | return sourceRefiner.merge(Tuple.of(key.get(0)), TruthValue.TRUE) && | ||
36 | targetRefiner.merge(Tuple.of(key.get(1)), TruthValue.TRUE); | ||
37 | } | ||
38 | return true; | ||
39 | } | ||
40 | |||
41 | public static Factory<TruthValue, Boolean> of(Symbol<TruthValue> concreteSymbol, PartialRelation sourceType, | ||
42 | PartialRelation targetType) { | ||
43 | return (adapter, partialSymbol) -> new DirectedCrossReferenceRefiner(adapter, partialSymbol, concreteSymbol, | ||
44 | sourceType, targetType); | ||
45 | } | ||
46 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java new file mode 100644 index 00000000..9028337c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java | |||
@@ -0,0 +1,94 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.Rule; | ||
9 | import tools.refinery.store.model.ModelStoreBuilder; | ||
10 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
13 | import tools.refinery.store.query.view.ForbiddenView; | ||
14 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
15 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
16 | import tools.refinery.store.reasoning.literal.Modality; | ||
17 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
20 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | ||
21 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
22 | import tools.refinery.store.representation.Symbol; | ||
23 | import tools.refinery.store.representation.TruthValue; | ||
24 | |||
25 | import static tools.refinery.store.query.literal.Literals.not; | ||
26 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | ||
27 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; | ||
28 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; | ||
29 | |||
30 | public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration { | ||
31 | private final PartialRelation linkType; | ||
32 | private final DirectedCrossReferenceInfo info; | ||
33 | private final Symbol<TruthValue> symbol; | ||
34 | |||
35 | public DirectedCrossReferenceTranslator(PartialRelation linkType, DirectedCrossReferenceInfo info) { | ||
36 | this.linkType = linkType; | ||
37 | this.info = info; | ||
38 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); | ||
39 | } | ||
40 | |||
41 | @Override | ||
42 | public void apply(ModelStoreBuilder storeBuilder) { | ||
43 | var name = linkType.name(); | ||
44 | var sourceType = info.sourceType(); | ||
45 | var targetType = info.targetType(); | ||
46 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); | ||
47 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); | ||
48 | var forbiddenView = new ForbiddenView(symbol); | ||
49 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
50 | |||
51 | storeBuilder.with(PartialRelationTranslator.of(linkType) | ||
52 | .symbol(symbol) | ||
53 | .may(Query.of(mayName, (builder, source, target) -> { | ||
54 | builder.clause( | ||
55 | mayNewSource.call(source), | ||
56 | mayNewTarget.call(target), | ||
57 | not(forbiddenView.call(source, target)) | ||
58 | ); | ||
59 | if (info.isConstrained()) { | ||
60 | // Violation of monotonicity: | ||
61 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
62 | // corresponding error pattern will already mark the node as invalid. | ||
63 | builder.clause( | ||
64 | must(linkType.call(source, target)), | ||
65 | not(forbiddenView.call(source, target)), | ||
66 | may(sourceType.call(source)), | ||
67 | may(targetType.call(target)) | ||
68 | ); | ||
69 | } | ||
70 | })) | ||
71 | .refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)) | ||
72 | .initializer(new RefinementBasedInitializer<>(linkType)) | ||
73 | .decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
74 | .clause( | ||
75 | may(linkType.call(source, target)), | ||
76 | not(candidateMust(linkType.call(source, target))), | ||
77 | not(MULTI_VIEW.call(source)), | ||
78 | not(MULTI_VIEW.call(target)) | ||
79 | ) | ||
80 | .action( | ||
81 | add(linkType, source, target) | ||
82 | )))); | ||
83 | |||
84 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, | ||
85 | info.sourceMultiplicity())); | ||
86 | |||
87 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(targetType, linkType, true, | ||
88 | info.targetMultiplicity())); | ||
89 | } | ||
90 | |||
91 | private RelationalQuery createMayHelper(PartialRelation type, Multiplicity multiplicity, boolean inverse) { | ||
92 | return CrossReferenceUtils.createMayHelper(linkType, type, multiplicity, inverse); | ||
93 | } | ||
94 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java new file mode 100644 index 00000000..fe99bc54 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java | |||
@@ -0,0 +1,15 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
10 | |||
11 | public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity) { | ||
12 | public boolean isConstrained() { | ||
13 | return multiplicity.isConstrained(); | ||
14 | } | ||
15 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java new file mode 100644 index 00000000..43c1462b --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java | |||
@@ -0,0 +1,44 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner; | ||
10 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
12 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
13 | import tools.refinery.store.representation.Symbol; | ||
14 | import tools.refinery.store.representation.TruthValue; | ||
15 | import tools.refinery.store.tuple.Tuple; | ||
16 | |||
17 | class UndirectedCrossReferenceRefiner extends ConcreteSymbolRefiner<TruthValue, Boolean> { | ||
18 | private final PartialInterpretationRefiner<TruthValue, Boolean> sourceRefiner; | ||
19 | |||
20 | public UndirectedCrossReferenceRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
21 | Symbol<TruthValue> concreteSymbol, PartialRelation sourceType) { | ||
22 | super(adapter, partialSymbol, concreteSymbol); | ||
23 | sourceRefiner = adapter.getRefiner(sourceType); | ||
24 | } | ||
25 | |||
26 | @Override | ||
27 | public boolean merge(Tuple key, TruthValue value) { | ||
28 | int source = key.get(0); | ||
29 | int target = key.get(1); | ||
30 | if (!super.merge(key, value) || !super.merge(Tuple.of(target, source), value)) { | ||
31 | return false; | ||
32 | } | ||
33 | if (value.must()) { | ||
34 | return sourceRefiner.merge(Tuple.of(source), TruthValue.TRUE) && | ||
35 | sourceRefiner.merge(Tuple.of(target), TruthValue.TRUE); | ||
36 | } | ||
37 | return true; | ||
38 | } | ||
39 | |||
40 | public static Factory<TruthValue, Boolean> of(Symbol<TruthValue> concreteSymbol, PartialRelation sourceType) { | ||
41 | return (adapter, partialSymbol) -> new UndirectedCrossReferenceRefiner(adapter, partialSymbol, concreteSymbol, | ||
42 | sourceType); | ||
43 | } | ||
44 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java new file mode 100644 index 00000000..c554e2a4 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java | |||
@@ -0,0 +1,83 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.crossreference; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.Rule; | ||
9 | import tools.refinery.store.model.ModelStoreBuilder; | ||
10 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.view.ForbiddenView; | ||
13 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.literal.Modality; | ||
16 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
19 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | import tools.refinery.store.representation.TruthValue; | ||
22 | |||
23 | import static tools.refinery.store.query.literal.Literals.not; | ||
24 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | ||
25 | import static tools.refinery.store.reasoning.literal.PartialLiterals.*; | ||
26 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW; | ||
27 | |||
28 | public class UndirectedCrossReferenceTranslator implements ModelStoreConfiguration { | ||
29 | private final PartialRelation linkType; | ||
30 | private final UndirectedCrossReferenceInfo info; | ||
31 | private final Symbol<TruthValue> symbol; | ||
32 | |||
33 | public UndirectedCrossReferenceTranslator(PartialRelation linkType, UndirectedCrossReferenceInfo info) { | ||
34 | this.linkType = linkType; | ||
35 | this.info = info; | ||
36 | symbol = Symbol.of(linkType.name(), 2, TruthValue.class, TruthValue.UNKNOWN); | ||
37 | } | ||
38 | |||
39 | @Override | ||
40 | public void apply(ModelStoreBuilder storeBuilder) { | ||
41 | var name = linkType.name(); | ||
42 | var type = info.type(); | ||
43 | var forbiddenView = new ForbiddenView(symbol); | ||
44 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
45 | |||
46 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); | ||
47 | |||
48 | storeBuilder.with(PartialRelationTranslator.of(linkType) | ||
49 | .symbol(symbol) | ||
50 | .may(Query.of(mayName, (builder, source, target) -> { | ||
51 | builder.clause( | ||
52 | mayNewSource.call(source), | ||
53 | mayNewSource.call(target), | ||
54 | not(forbiddenView.call(source, target)) | ||
55 | ); | ||
56 | if (info.isConstrained()) { | ||
57 | // Violation of monotonicity: | ||
58 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
59 | // corresponding error pattern will already mark the node as invalid. | ||
60 | builder.clause( | ||
61 | must(linkType.call(source, target)), | ||
62 | not(forbiddenView.call(source, target)), | ||
63 | may(type.call(source)), | ||
64 | may(type.call(target)) | ||
65 | ); | ||
66 | } | ||
67 | })) | ||
68 | .refiner(UndirectedCrossReferenceRefiner.of(symbol, type)) | ||
69 | .initializer(new RefinementBasedInitializer<>(linkType)) | ||
70 | .decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
71 | .clause( | ||
72 | may(linkType.call(source, target)), | ||
73 | not(candidateMust(linkType.call(source, target))), | ||
74 | not(MULTI_VIEW.call(source)), | ||
75 | not(MULTI_VIEW.call(target)) | ||
76 | ) | ||
77 | .action( | ||
78 | add(linkType, source, target) | ||
79 | )))); | ||
80 | |||
81 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); | ||
82 | } | ||
83 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java new file mode 100644 index 00000000..a21da3d4 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java | |||
@@ -0,0 +1,33 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | ||
11 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder; | ||
12 | |||
13 | import java.util.Collection; | ||
14 | |||
15 | public class ContainedTypeHierarchyBuilder extends TypeHierarchyBuilder { | ||
16 | ContainedTypeHierarchyBuilder() { | ||
17 | } | ||
18 | |||
19 | boolean isInvalidType(PartialRelation type) { | ||
20 | return !typeInfoMap.containsKey(type); | ||
21 | } | ||
22 | |||
23 | void setContainedTypes(Collection<PartialRelation> containedTypes) { | ||
24 | for (var containedType : containedTypes) { | ||
25 | var currentInfo = typeInfoMap.get(containedType); | ||
26 | if (currentInfo == null) { | ||
27 | throw new TranslationException(containedType, "Invalid contained type: " + containedType); | ||
28 | } | ||
29 | var newInfo = currentInfo.addSupertype(ContainmentHierarchyTranslator.CONTAINED_SYMBOL); | ||
30 | typeInfoMap.put(containedType, newInfo); | ||
31 | } | ||
32 | } | ||
33 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java new file mode 100644 index 00000000..72b836ff --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java | |||
@@ -0,0 +1,23 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.containment.ContainmentInfo; | ||
10 | import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo; | ||
11 | import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo; | ||
12 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy; | ||
13 | |||
14 | import java.util.Map; | ||
15 | |||
16 | public record Metamodel(TypeHierarchy typeHierarchy, Map<PartialRelation, ContainmentInfo> containmentHierarchy, | ||
17 | Map<PartialRelation, DirectedCrossReferenceInfo> directedCrossReferences, | ||
18 | Map<PartialRelation, UndirectedCrossReferenceInfo> undirectedCrossReferences, | ||
19 | Map<PartialRelation, PartialRelation> oppositeReferences) { | ||
20 | public static MetamodelBuilder builder() { | ||
21 | return new MetamodelBuilder(); | ||
22 | } | ||
23 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java new file mode 100644 index 00000000..ad0288ed --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java | |||
@@ -0,0 +1,225 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | ||
11 | import tools.refinery.store.reasoning.translator.containment.ContainmentInfo; | ||
12 | import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo; | ||
13 | import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo; | ||
14 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
15 | import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; | ||
16 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo; | ||
17 | |||
18 | import java.util.*; | ||
19 | |||
20 | public class MetamodelBuilder { | ||
21 | private final ContainedTypeHierarchyBuilder typeHierarchyBuilder = new ContainedTypeHierarchyBuilder(); | ||
22 | private final Map<PartialRelation, ReferenceInfo> referenceInfoMap = new LinkedHashMap<>(); | ||
23 | private final Set<PartialRelation> containedTypes = new HashSet<>(); | ||
24 | private final Map<PartialRelation, ContainmentInfo> containmentHierarchy = new LinkedHashMap<>(); | ||
25 | private final Map<PartialRelation, DirectedCrossReferenceInfo> directedCrossReferences = new LinkedHashMap<>(); | ||
26 | private final Map<PartialRelation, UndirectedCrossReferenceInfo> undirectedCrossReferences = new LinkedHashMap<>(); | ||
27 | private final Map<PartialRelation, PartialRelation> oppositeReferences = new LinkedHashMap<>(); | ||
28 | |||
29 | MetamodelBuilder() { | ||
30 | typeHierarchyBuilder.type(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, true); | ||
31 | } | ||
32 | |||
33 | public MetamodelBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) { | ||
34 | typeHierarchyBuilder.type(partialRelation, typeInfo); | ||
35 | return this; | ||
36 | |||
37 | } | ||
38 | |||
39 | public MetamodelBuilder type(PartialRelation partialRelation, boolean abstractType, | ||
40 | PartialRelation... supertypes) { | ||
41 | typeHierarchyBuilder.type(partialRelation, abstractType, supertypes); | ||
42 | return this; | ||
43 | } | ||
44 | |||
45 | public MetamodelBuilder type(PartialRelation partialRelation, boolean abstractType, | ||
46 | Collection<PartialRelation> supertypes) { | ||
47 | typeHierarchyBuilder.type(partialRelation, abstractType, supertypes); | ||
48 | return this; | ||
49 | } | ||
50 | |||
51 | public MetamodelBuilder type(PartialRelation partialRelation, PartialRelation... supertypes) { | ||
52 | typeHierarchyBuilder.type(partialRelation, supertypes); | ||
53 | return this; | ||
54 | } | ||
55 | |||
56 | public MetamodelBuilder type(PartialRelation partialRelation, Collection<PartialRelation> supertypes) { | ||
57 | typeHierarchyBuilder.type(partialRelation, supertypes); | ||
58 | return this; | ||
59 | } | ||
60 | |||
61 | public MetamodelBuilder types(Collection<Map.Entry<PartialRelation, TypeInfo>> entries) { | ||
62 | typeHierarchyBuilder.types(entries); | ||
63 | return this; | ||
64 | } | ||
65 | |||
66 | public MetamodelBuilder types(Map<PartialRelation, TypeInfo> map) { | ||
67 | typeHierarchyBuilder.types(map); | ||
68 | return this; | ||
69 | } | ||
70 | |||
71 | public MetamodelBuilder reference(PartialRelation linkType, ReferenceInfo info) { | ||
72 | if (linkType.arity() != 2) { | ||
73 | throw new TranslationException(linkType, | ||
74 | "Only references of arity 2 are supported, got %s with %d instead".formatted( | ||
75 | linkType, linkType.arity())); | ||
76 | } | ||
77 | var putResult = referenceInfoMap.put(linkType, info); | ||
78 | if (putResult != null && !putResult.equals(info)) { | ||
79 | throw new TranslationException(linkType, "Duplicate reference info for partial relation: " + linkType); | ||
80 | } | ||
81 | return this; | ||
82 | } | ||
83 | |||
84 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
85 | Multiplicity multiplicity, PartialRelation targetType, | ||
86 | PartialRelation opposite) { | ||
87 | return reference(linkType, new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite)); | ||
88 | } | ||
89 | |||
90 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity, | ||
91 | PartialRelation targetType, PartialRelation opposite) { | ||
92 | return reference(linkType, sourceType, false, multiplicity, targetType, opposite); | ||
93 | } | ||
94 | |||
95 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, | ||
96 | boolean containment, PartialRelation targetType, PartialRelation opposite) { | ||
97 | return reference(linkType, sourceType, containment, UnconstrainedMultiplicity.INSTANCE, targetType, opposite); | ||
98 | } | ||
99 | |||
100 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, PartialRelation targetType, | ||
101 | PartialRelation opposite) { | ||
102 | return reference(linkType, sourceType, UnconstrainedMultiplicity.INSTANCE, targetType, opposite); | ||
103 | } | ||
104 | |||
105 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
106 | Multiplicity multiplicity, PartialRelation targetType) { | ||
107 | return reference(linkType, sourceType, containment, multiplicity, targetType, null); | ||
108 | } | ||
109 | |||
110 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, Multiplicity multiplicity, | ||
111 | PartialRelation targetType) { | ||
112 | return reference(linkType, sourceType, multiplicity, targetType, null); | ||
113 | } | ||
114 | |||
115 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, boolean containment, | ||
116 | PartialRelation targetType) { | ||
117 | return reference(linkType, sourceType, containment, targetType, null); | ||
118 | } | ||
119 | |||
120 | public MetamodelBuilder reference(PartialRelation linkType, PartialRelation sourceType, | ||
121 | PartialRelation targetType) { | ||
122 | return reference(linkType, sourceType, targetType, null); | ||
123 | } | ||
124 | |||
125 | public MetamodelBuilder references(Collection<Map.Entry<PartialRelation, ReferenceInfo>> entries) { | ||
126 | for (var entry : entries) { | ||
127 | reference(entry.getKey(), entry.getValue()); | ||
128 | } | ||
129 | return this; | ||
130 | } | ||
131 | |||
132 | public MetamodelBuilder references(Map<PartialRelation, ReferenceInfo> map) { | ||
133 | return references(map.entrySet()); | ||
134 | } | ||
135 | |||
136 | public Metamodel build() { | ||
137 | for (var entry : referenceInfoMap.entrySet()) { | ||
138 | var linkType = entry.getKey(); | ||
139 | var info = entry.getValue(); | ||
140 | processReferenceInfo(linkType, info); | ||
141 | } | ||
142 | typeHierarchyBuilder.setContainedTypes(containedTypes); | ||
143 | var typeHierarchy = typeHierarchyBuilder.build(); | ||
144 | return new Metamodel(typeHierarchy, Collections.unmodifiableMap(containmentHierarchy), | ||
145 | Collections.unmodifiableMap(directedCrossReferences), | ||
146 | Collections.unmodifiableMap(undirectedCrossReferences), | ||
147 | Collections.unmodifiableMap(oppositeReferences)); | ||
148 | } | ||
149 | |||
150 | private void processReferenceInfo(PartialRelation linkType, ReferenceInfo info) { | ||
151 | if (oppositeReferences.containsKey(linkType) || containmentHierarchy.containsKey(linkType)) { | ||
152 | // We already processed this reference while processing its opposite. | ||
153 | return; | ||
154 | } | ||
155 | var sourceType = info.sourceType(); | ||
156 | var targetType = info.targetType(); | ||
157 | if (typeHierarchyBuilder.isInvalidType(sourceType)) { | ||
158 | throw new TranslationException(linkType, "Source type %s of %s is not in type hierarchy" | ||
159 | .formatted(sourceType, linkType)); | ||
160 | } | ||
161 | if (typeHierarchyBuilder.isInvalidType(targetType)) { | ||
162 | throw new TranslationException(linkType, "Target type %s of %s is not in type hierarchy" | ||
163 | .formatted(targetType, linkType)); | ||
164 | } | ||
165 | var opposite = info.opposite(); | ||
166 | Multiplicity targetMultiplicity = UnconstrainedMultiplicity.INSTANCE; | ||
167 | if (opposite != null) { | ||
168 | var oppositeInfo = referenceInfoMap.get(opposite); | ||
169 | validateOpposite(linkType, info, opposite, oppositeInfo); | ||
170 | targetMultiplicity = oppositeInfo.multiplicity(); | ||
171 | if (oppositeInfo.containment()) { | ||
172 | // Skip processing this reference and process it once we encounter its containment opposite. | ||
173 | return; | ||
174 | } | ||
175 | if (opposite.equals(linkType)) { | ||
176 | if (!sourceType.equals(targetType)) { | ||
177 | throw new TranslationException(linkType, | ||
178 | "Target %s of undirected reference %s differs from source %s".formatted( | ||
179 | targetType, linkType, sourceType)); | ||
180 | } | ||
181 | undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, | ||
182 | info.multiplicity())); | ||
183 | return; | ||
184 | } | ||
185 | oppositeReferences.put(opposite, linkType); | ||
186 | } | ||
187 | if (info.containment()) { | ||
188 | if (!UnconstrainedMultiplicity.INSTANCE.equals(targetMultiplicity)) { | ||
189 | throw new TranslationException(opposite, "Invalid opposite %s with multiplicity %s of containment %s" | ||
190 | .formatted(opposite, targetMultiplicity, linkType)); | ||
191 | } | ||
192 | containedTypes.add(targetType); | ||
193 | containmentHierarchy.put(linkType, new ContainmentInfo(sourceType, info.multiplicity(), targetType)); | ||
194 | return; | ||
195 | } | ||
196 | directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), | ||
197 | targetType, targetMultiplicity)); | ||
198 | } | ||
199 | |||
200 | private static void validateOpposite(PartialRelation linkType, ReferenceInfo info, PartialRelation opposite, | ||
201 | ReferenceInfo oppositeInfo) { | ||
202 | var sourceType = info.sourceType(); | ||
203 | var targetType = info.targetType(); | ||
204 | if (oppositeInfo == null) { | ||
205 | throw new TranslationException(linkType, "Opposite %s of %s is not defined" | ||
206 | .formatted(opposite, linkType)); | ||
207 | } | ||
208 | if (!linkType.equals(oppositeInfo.opposite())) { | ||
209 | throw new TranslationException(opposite, "Expected %s to have opposite %s, got %s instead" | ||
210 | .formatted(opposite, linkType, oppositeInfo.opposite())); | ||
211 | } | ||
212 | if (!targetType.equals(oppositeInfo.sourceType())) { | ||
213 | throw new TranslationException(linkType, "Expected %s to have source type %s, got %s instead" | ||
214 | .formatted(opposite, targetType, oppositeInfo.sourceType())); | ||
215 | } | ||
216 | if (!sourceType.equals(oppositeInfo.targetType())) { | ||
217 | throw new TranslationException(linkType, "Expected %s to have target type %s, got %s instead" | ||
218 | .formatted(opposite, sourceType, oppositeInfo.targetType())); | ||
219 | } | ||
220 | if (oppositeInfo.containment() && info.containment()) { | ||
221 | throw new TranslationException(opposite, "Opposite %s of containment %s cannot be containment" | ||
222 | .formatted(opposite, linkType)); | ||
223 | } | ||
224 | } | ||
225 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java new file mode 100644 index 00000000..5afa58f2 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStoreBuilder; | ||
9 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
10 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | ||
11 | import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceTranslator; | ||
12 | import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceTranslator; | ||
13 | import tools.refinery.store.reasoning.translator.opposite.OppositeRelationTranslator; | ||
14 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator; | ||
15 | |||
16 | public class MetamodelTranslator implements ModelStoreConfiguration { | ||
17 | private final Metamodel metamodel; | ||
18 | |||
19 | public MetamodelTranslator(Metamodel metamodel) { | ||
20 | this.metamodel = metamodel; | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public void apply(ModelStoreBuilder storeBuilder) { | ||
25 | storeBuilder.with(new TypeHierarchyTranslator(metamodel.typeHierarchy())); | ||
26 | storeBuilder.with(new ContainmentHierarchyTranslator(metamodel.containmentHierarchy())); | ||
27 | for (var entry : metamodel.directedCrossReferences().entrySet()) { | ||
28 | storeBuilder.with(new DirectedCrossReferenceTranslator(entry.getKey(), entry.getValue())); | ||
29 | } | ||
30 | for (var entry : metamodel.undirectedCrossReferences().entrySet()) { | ||
31 | storeBuilder.with(new UndirectedCrossReferenceTranslator(entry.getKey(), entry.getValue())); | ||
32 | } | ||
33 | for (var entry : metamodel.oppositeReferences().entrySet()) { | ||
34 | storeBuilder.with(new OppositeRelationTranslator(entry.getKey(), entry.getValue())); | ||
35 | } | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java new file mode 100644 index 00000000..9a6b4012 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java | |||
@@ -0,0 +1,13 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | ||
10 | |||
11 | public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, | ||
12 | PartialRelation targetType, PartialRelation opposite) { | ||
13 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java new file mode 100644 index 00000000..d8db4ec4 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java | |||
@@ -0,0 +1,64 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
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 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java new file mode 100644 index 00000000..9db9cc96 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiplicity; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
11 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
12 | import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval; | ||
13 | |||
14 | public record ConstrainedMultiplicity(NonEmptyCardinalityInterval multiplicity, PartialRelation errorSymbol) | ||
15 | implements Multiplicity { | ||
16 | public ConstrainedMultiplicity { | ||
17 | if (multiplicity.equals(CardinalityIntervals.SET)) { | ||
18 | throw new TranslationException(errorSymbol, "Expected a constrained cardinality interval"); | ||
19 | } | ||
20 | if (errorSymbol.arity() != 1) { | ||
21 | throw new TranslationException(errorSymbol, "Expected error symbol %s to have arity 1, got %d instead" | ||
22 | .formatted(errorSymbol, errorSymbol.arity())); | ||
23 | } | ||
24 | } | ||
25 | |||
26 | public static ConstrainedMultiplicity of(CardinalityInterval multiplicity, PartialRelation errorSymbol) { | ||
27 | if (!(multiplicity instanceof NonEmptyCardinalityInterval nonEmptyCardinalityInterval)) { | ||
28 | throw new TranslationException(errorSymbol, "Inconsistent multiplicity"); | ||
29 | } | ||
30 | return new ConstrainedMultiplicity(nonEmptyCardinalityInterval, errorSymbol); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public boolean isConstrained() { | ||
35 | return true; | ||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java new file mode 100644 index 00000000..ba208156 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java | |||
@@ -0,0 +1,141 @@ | |||
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.multiplicity; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.objectives.Objectives; | ||
9 | import tools.refinery.store.model.ModelStoreBuilder; | ||
10 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.term.int_.IntTerms; | ||
14 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
15 | import tools.refinery.store.reasoning.lifting.DnfLifter; | ||
16 | import tools.refinery.store.reasoning.literal.*; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
19 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
20 | import tools.refinery.store.representation.cardinality.FiniteUpperCardinality; | ||
21 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
22 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
23 | |||
24 | import java.util.List; | ||
25 | |||
26 | import static tools.refinery.store.query.literal.Literals.check; | ||
27 | import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; | ||
28 | import static tools.refinery.store.query.term.int_.IntTerms.constant; | ||
29 | import static tools.refinery.store.query.term.int_.IntTerms.greater; | ||
30 | import static tools.refinery.store.query.term.int_.IntTerms.sub; | ||
31 | import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant; | ||
32 | import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.less; | ||
33 | import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust; | ||
34 | import static tools.refinery.store.reasoning.literal.PartialLiterals.must; | ||
35 | |||
36 | public class InvalidMultiplicityErrorTranslator implements ModelStoreConfiguration { | ||
37 | private final PartialRelation nodeType; | ||
38 | private final PartialRelation linkType; | ||
39 | private final boolean inverse; | ||
40 | private final Multiplicity multiplicity; | ||
41 | |||
42 | public InvalidMultiplicityErrorTranslator(PartialRelation nodeType, PartialRelation linkType, | ||
43 | boolean inverse, Multiplicity multiplicity) { | ||
44 | if (nodeType.arity() != 1) { | ||
45 | throw new TranslationException(linkType, "Node type must be of arity 1, got %s with arity %d instead" | ||
46 | .formatted(nodeType, nodeType.arity())); | ||
47 | } | ||
48 | if (linkType.arity() != 2) { | ||
49 | throw new TranslationException(linkType, "Link type must be of arity 2, got %s with arity %d instead" | ||
50 | .formatted(linkType, linkType.arity())); | ||
51 | } | ||
52 | this.nodeType = nodeType; | ||
53 | this.linkType = linkType; | ||
54 | this.inverse = inverse; | ||
55 | this.multiplicity = multiplicity; | ||
56 | } | ||
57 | |||
58 | @Override | ||
59 | public void apply(ModelStoreBuilder storeBuilder) { | ||
60 | if (!(multiplicity instanceof ConstrainedMultiplicity constrainedMultiplicity)) { | ||
61 | return; | ||
62 | } | ||
63 | |||
64 | var name = constrainedMultiplicity.errorSymbol().name(); | ||
65 | var cardinalityInterval = constrainedMultiplicity.multiplicity(); | ||
66 | var node = Variable.of("node"); | ||
67 | var other = Variable.of("other"); | ||
68 | List<Variable> arguments = inverse ? List.of(other, node) : List.of(node, other); | ||
69 | var mustBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL)) | ||
70 | .parameter(node); | ||
71 | var candidateMayBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL)) | ||
72 | .parameter(node); | ||
73 | var candidateMustBuilder = Query.builder(DnfLifter.decorateName(name, Modality.MUST, Concreteness.PARTIAL)) | ||
74 | .parameter(node); | ||
75 | var missingOutput = Variable.of("missing", Integer.class); | ||
76 | var missingBuilder = Query.builder(name + "#missingMultiplicity").parameter(node).output(missingOutput); | ||
77 | |||
78 | int lowerBound = cardinalityInterval.lowerBound(); | ||
79 | if (lowerBound > 0) { | ||
80 | var lowerBoundCardinality = UpperCardinalities.atMost(lowerBound); | ||
81 | mustBuilder.clause(UpperCardinality.class, existingContents -> List.of( | ||
82 | must(nodeType.call(node)), | ||
83 | new CountUpperBoundLiteral(existingContents, linkType, arguments), | ||
84 | check(less(existingContents, constant(lowerBoundCardinality))) | ||
85 | )); | ||
86 | candidateMayBuilder.clause(Integer.class, existingContents -> List.of( | ||
87 | candidateMust(nodeType.call(node)), | ||
88 | new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments), | ||
89 | check(IntTerms.less(existingContents, constant(lowerBound))) | ||
90 | )); | ||
91 | candidateMustBuilder.clause(Integer.class, existingContents -> List.of( | ||
92 | candidateMust(nodeType.call(node)), | ||
93 | new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments), | ||
94 | check(IntTerms.less(existingContents, constant(lowerBound))) | ||
95 | )); | ||
96 | missingBuilder.clause(Integer.class, existingContents -> List.of( | ||
97 | candidateMust(nodeType.call(node)), | ||
98 | new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments), | ||
99 | missingOutput.assign(sub(constant(lowerBound), existingContents)), | ||
100 | check(greater(missingOutput, constant(0))) | ||
101 | )); | ||
102 | } | ||
103 | |||
104 | if (cardinalityInterval.upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) { | ||
105 | int upperBound = finiteUpperCardinality.finiteUpperBound(); | ||
106 | mustBuilder.clause(Integer.class, existingContents -> List.of( | ||
107 | must(nodeType.call(node)), | ||
108 | new CountLowerBoundLiteral(existingContents, linkType, arguments), | ||
109 | check(greater(existingContents, constant(upperBound))) | ||
110 | )); | ||
111 | candidateMayBuilder.clause(Integer.class, existingContents -> List.of( | ||
112 | candidateMust(nodeType.call(node)), | ||
113 | new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments), | ||
114 | check(greater(existingContents, constant(upperBound))) | ||
115 | )); | ||
116 | candidateMustBuilder.clause(Integer.class, existingContents -> List.of( | ||
117 | candidateMust(nodeType.call(node)), | ||
118 | new CountCandidateLowerBoundLiteral(existingContents, linkType, arguments), | ||
119 | check(greater(existingContents, constant(upperBound))) | ||
120 | )); | ||
121 | missingBuilder.clause(Integer.class, existingContents -> List.of( | ||
122 | candidateMust(nodeType.call(node)), | ||
123 | candidateMust(ReasoningAdapter.EXISTS_SYMBOL.call(node)), | ||
124 | new CountCandidateUpperBoundLiteral(existingContents, linkType, arguments), | ||
125 | missingOutput.assign(sub(existingContents, constant(upperBound))), | ||
126 | check(greater(missingOutput, constant(0))) | ||
127 | )); | ||
128 | } | ||
129 | |||
130 | var objective = Query.of(name + "#objective", Integer.class, (builder, output) -> builder.clause( | ||
131 | output.assign(missingBuilder.build().aggregate(INT_SUM, Variable.of())) | ||
132 | )); | ||
133 | |||
134 | storeBuilder.with(PartialRelationTranslator.of(constrainedMultiplicity.errorSymbol()) | ||
135 | .mayNever() | ||
136 | .must(mustBuilder.build()) | ||
137 | .candidateMay(candidateMayBuilder.build()) | ||
138 | .candidateMust(candidateMustBuilder.build()) | ||
139 | .objective(Objectives.value(objective))); | ||
140 | } | ||
141 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java new file mode 100644 index 00000000..d1d6dd1e --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiplicity; | ||
7 | |||
8 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
9 | |||
10 | public sealed interface Multiplicity permits ConstrainedMultiplicity, UnconstrainedMultiplicity { | ||
11 | CardinalityInterval multiplicity(); | ||
12 | |||
13 | boolean isConstrained(); | ||
14 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java new file mode 100644 index 00000000..2159b88c --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java | |||
@@ -0,0 +1,28 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiplicity; | ||
7 | |||
8 | import tools.refinery.store.representation.cardinality.CardinalityInterval; | ||
9 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
10 | |||
11 | // Singleton implementation, because there is only a single complete interval. | ||
12 | @SuppressWarnings("squid:S6548") | ||
13 | public final class UnconstrainedMultiplicity implements Multiplicity { | ||
14 | public static final UnconstrainedMultiplicity INSTANCE = new UnconstrainedMultiplicity(); | ||
15 | |||
16 | private UnconstrainedMultiplicity() { | ||
17 | } | ||
18 | |||
19 | @Override | ||
20 | public CardinalityInterval multiplicity() { | ||
21 | return CardinalityIntervals.SET; | ||
22 | } | ||
23 | |||
24 | @Override | ||
25 | public boolean isConstrained() { | ||
26 | return true; | ||
27 | } | ||
28 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java new file mode 100644 index 00000000..7290ab40 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java | |||
@@ -0,0 +1,77 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.opposite; | ||
7 | |||
8 | |||
9 | import tools.refinery.store.map.AnyVersionedMap; | ||
10 | import tools.refinery.store.map.Cursor; | ||
11 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
12 | import tools.refinery.store.reasoning.interpretation.AbstractPartialInterpretation; | ||
13 | import tools.refinery.store.reasoning.interpretation.PartialInterpretation; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
16 | import tools.refinery.store.tuple.Tuple; | ||
17 | |||
18 | import java.util.Set; | ||
19 | |||
20 | class OppositeInterpretation<A, C> extends AbstractPartialInterpretation<A, C> { | ||
21 | private final PartialInterpretation<A, C> opposite; | ||
22 | |||
23 | private OppositeInterpretation(ReasoningAdapter adapter, Concreteness concreteness, | ||
24 | PartialSymbol<A, C> partialSymbol, PartialInterpretation<A, C> opposite) { | ||
25 | super(adapter, concreteness, partialSymbol); | ||
26 | this.opposite = opposite; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public A get(Tuple key) { | ||
31 | return opposite.get(OppositeUtils.flip(key)); | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public Cursor<Tuple, A> getAll() { | ||
36 | return new OppositeCursor<>(opposite.getAll()); | ||
37 | } | ||
38 | |||
39 | public static <A1, C1> Factory<A1, C1> of(PartialSymbol<A1, C1> oppositeSymbol) { | ||
40 | return (adapter, concreteness, partialSymbol) -> { | ||
41 | var opposite = adapter.getPartialInterpretation(concreteness, oppositeSymbol); | ||
42 | return new OppositeInterpretation<>(adapter, concreteness, partialSymbol, opposite); | ||
43 | }; | ||
44 | } | ||
45 | |||
46 | private record OppositeCursor<T>(Cursor<Tuple, T> opposite) implements Cursor<Tuple, T> { | ||
47 | @Override | ||
48 | public Tuple getKey() { | ||
49 | return OppositeUtils.flip(opposite.getKey()); | ||
50 | } | ||
51 | |||
52 | @Override | ||
53 | public T getValue() { | ||
54 | return opposite.getValue(); | ||
55 | } | ||
56 | |||
57 | @Override | ||
58 | public boolean isTerminated() { | ||
59 | return opposite.isTerminated(); | ||
60 | } | ||
61 | |||
62 | @Override | ||
63 | public boolean move() { | ||
64 | return opposite.move(); | ||
65 | } | ||
66 | |||
67 | @Override | ||
68 | public Set<AnyVersionedMap> getDependingMaps() { | ||
69 | return opposite.getDependingMaps(); | ||
70 | } | ||
71 | |||
72 | @Override | ||
73 | public boolean isDirty() { | ||
74 | return opposite.isDirty(); | ||
75 | } | ||
76 | } | ||
77 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java new file mode 100644 index 00000000..d09684df --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.opposite; | ||
7 | |||
8 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
9 | import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner; | ||
10 | import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner; | ||
11 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
12 | import tools.refinery.store.tuple.Tuple; | ||
13 | |||
14 | public class OppositeRefiner<A, C> extends AbstractPartialInterpretationRefiner<A, C> { | ||
15 | private final PartialInterpretationRefiner<A, C> opposite; | ||
16 | |||
17 | protected OppositeRefiner(ReasoningAdapter adapter, PartialSymbol<A, C> partialSymbol, | ||
18 | PartialSymbol<A, C> oppositeSymbol) { | ||
19 | super(adapter, partialSymbol); | ||
20 | opposite = adapter.getRefiner(oppositeSymbol); | ||
21 | } | ||
22 | |||
23 | @Override | ||
24 | public boolean merge(Tuple key, A value) { | ||
25 | var oppositeKey = OppositeUtils.flip(key); | ||
26 | return opposite.merge(oppositeKey, value); | ||
27 | } | ||
28 | |||
29 | public static <A1, C1> Factory<A1, C1> of(PartialSymbol<A1, C1> oppositeSymbol) { | ||
30 | return (adapter, partialSymbol) -> new OppositeRefiner<>(adapter, partialSymbol, oppositeSymbol); | ||
31 | } | ||
32 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java new file mode 100644 index 00000000..6e15a628 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.opposite; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStoreBuilder; | ||
9 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
10 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
16 | import tools.refinery.store.reasoning.literal.Modality; | ||
17 | import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer; | ||
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
20 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
21 | |||
22 | import java.util.List; | ||
23 | import java.util.Set; | ||
24 | |||
25 | public class OppositeRelationTranslator implements ModelStoreConfiguration, PartialRelationRewriter { | ||
26 | private final PartialRelation linkType; | ||
27 | private final PartialRelation opposite; | ||
28 | |||
29 | public OppositeRelationTranslator(PartialRelation linkType, PartialRelation opposite) { | ||
30 | if (linkType.arity() != 2) { | ||
31 | throw new TranslationException(linkType, | ||
32 | "Expected relation with opposite %s to have arity 2, got %d instead" | ||
33 | .formatted(linkType, linkType.arity())); | ||
34 | } | ||
35 | if (opposite.arity() != 2) { | ||
36 | throw new TranslationException(linkType, | ||
37 | "Expected opposite %s of %s to have arity 2, got %d instead" | ||
38 | .formatted(opposite, linkType, opposite.arity())); | ||
39 | } | ||
40 | this.linkType = linkType; | ||
41 | this.opposite = opposite; | ||
42 | } | ||
43 | |||
44 | @Override | ||
45 | public void apply(ModelStoreBuilder storeBuilder) { | ||
46 | storeBuilder.with(PartialRelationTranslator.of(linkType) | ||
47 | .rewriter(this) | ||
48 | .interpretation(OppositeInterpretation.of(opposite)) | ||
49 | .refiner(OppositeRefiner.of(opposite)) | ||
50 | .initializer(new RefinementBasedInitializer<>(linkType))); | ||
51 | } | ||
52 | |||
53 | @Override | ||
54 | public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, | ||
55 | Modality modality, Concreteness concreteness) { | ||
56 | var arguments = literal.getArguments(); | ||
57 | var newArguments = List.of(arguments.get(1), arguments.get(0)); | ||
58 | var modalOpposite = new ModalConstraint(modality, concreteness, opposite); | ||
59 | var oppositeLiteral = literal.withArguments(modalOpposite, newArguments); | ||
60 | return List.of(oppositeLiteral); | ||
61 | } | ||
62 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java new file mode 100644 index 00000000..2a9e6b5d --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.opposite; | ||
7 | |||
8 | import tools.refinery.store.tuple.Tuple; | ||
9 | import tools.refinery.store.tuple.Tuple2; | ||
10 | |||
11 | final class OppositeUtils { | ||
12 | private OppositeUtils() { | ||
13 | throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); | ||
14 | } | ||
15 | |||
16 | public static Tuple flip(Tuple tuple) { | ||
17 | if (!(tuple instanceof Tuple2 tuple2)) { | ||
18 | throw new IllegalArgumentException("Cannot flip tuple: " + tuple); | ||
19 | } | ||
20 | return Tuple.of(tuple2.value1(), tuple2.value0()); | ||
21 | } | ||
22 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java new file mode 100644 index 00000000..b401118e --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java | |||
@@ -0,0 +1,93 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.predicate; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStoreBuilder; | ||
9 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
10 | import tools.refinery.store.query.dnf.Query; | ||
11 | import tools.refinery.store.query.dnf.RelationalQuery; | ||
12 | import tools.refinery.store.query.literal.Literal; | ||
13 | import tools.refinery.store.query.term.NodeVariable; | ||
14 | import tools.refinery.store.query.term.Variable; | ||
15 | import tools.refinery.store.query.view.ForbiddenView; | ||
16 | import tools.refinery.store.query.view.MayView; | ||
17 | import tools.refinery.store.query.view.MustView; | ||
18 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
20 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
21 | import tools.refinery.store.representation.Symbol; | ||
22 | import tools.refinery.store.representation.TruthValue; | ||
23 | |||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | ||
26 | import static tools.refinery.store.reasoning.literal.PartialLiterals.must; | ||
27 | |||
28 | public class PredicateTranslator implements ModelStoreConfiguration { | ||
29 | private final PartialRelation relation; | ||
30 | private final RelationalQuery query; | ||
31 | private final boolean mutable; | ||
32 | private final TruthValue defaultValue; | ||
33 | |||
34 | public PredicateTranslator(PartialRelation relation, RelationalQuery query, boolean mutable, | ||
35 | TruthValue defaultValue) { | ||
36 | if (relation.arity() != query.arity()) { | ||
37 | throw new TranslationException(relation, "Expected arity %d query for partial relation %s, got %d instead" | ||
38 | .formatted(relation.arity(), relation, query.arity())); | ||
39 | } | ||
40 | if (defaultValue.must()) { | ||
41 | throw new TranslationException(relation, "Default value must be UNKNOWN or FALSE"); | ||
42 | } | ||
43 | this.relation = relation; | ||
44 | this.query = query; | ||
45 | this.mutable = mutable; | ||
46 | this.defaultValue = defaultValue; | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public void apply(ModelStoreBuilder storeBuilder) { | ||
51 | var translator = PartialRelationTranslator.of(relation) | ||
52 | .query(query); | ||
53 | if (mutable) { | ||
54 | var symbol = Symbol.of(relation.name(), relation.arity(), TruthValue.class, defaultValue); | ||
55 | translator.symbol(symbol); | ||
56 | |||
57 | var parameters = new NodeVariable[relation.arity()]; | ||
58 | for (int i = 0; i < parameters.length; i++) { | ||
59 | parameters[i] = Variable.of("p" + i); | ||
60 | } | ||
61 | |||
62 | var must = Query.builder() | ||
63 | .parameters(parameters) | ||
64 | .clause(must(query.call(parameters))) | ||
65 | .clause(new MustView(symbol).call(parameters)) | ||
66 | .build(); | ||
67 | translator.must(must); | ||
68 | |||
69 | var mayLiterals = new Literal[2]; | ||
70 | mayLiterals[0] = may(query.call(parameters)); | ||
71 | if (defaultValue.may()) { | ||
72 | mayLiterals[1] = not(new ForbiddenView(symbol).call(parameters)); | ||
73 | } else { | ||
74 | mayLiterals[1] = new MayView(symbol).call(parameters); | ||
75 | } | ||
76 | var may = Query.builder() | ||
77 | .parameters(parameters) | ||
78 | .clause(mayLiterals) | ||
79 | .build(); | ||
80 | translator.may(may); | ||
81 | } else if (defaultValue.may()) { | ||
82 | // If all values are permitted, we don't need to check for any forbidden values in the model. | ||
83 | // If the result of this predicate of {@code ERROR}, some other partial relation (that we check for) | ||
84 | // will be {@code ERROR} as well. | ||
85 | translator.exclude(null); | ||
86 | translator.accept(null); | ||
87 | translator.objective(null); | ||
88 | } else { | ||
89 | translator.mayNever(); | ||
90 | } | ||
91 | storeBuilder.with(translator); | ||
92 | } | ||
93 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java new file mode 100644 index 00000000..45dc5bd2 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java | |||
@@ -0,0 +1,52 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.proxy; | ||
7 | |||
8 | import tools.refinery.store.model.ModelStoreBuilder; | ||
9 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
10 | import tools.refinery.store.query.literal.AbstractCallLiteral; | ||
11 | import tools.refinery.store.query.literal.Literal; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
16 | import tools.refinery.store.reasoning.literal.Modality; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
19 | |||
20 | import java.util.List; | ||
21 | import java.util.Set; | ||
22 | |||
23 | public class PartialRelationTranslatorProxy implements ModelStoreConfiguration, PartialRelationRewriter { | ||
24 | private final PartialRelation partialRelation; | ||
25 | private final PartialRelation targetRelation; | ||
26 | private final boolean mutable; | ||
27 | |||
28 | public PartialRelationTranslatorProxy(PartialRelation partialRelation, PartialRelation targetRelation, | ||
29 | boolean mutable) { | ||
30 | this.partialRelation = partialRelation; | ||
31 | this.targetRelation = targetRelation; | ||
32 | this.mutable = mutable; | ||
33 | } | ||
34 | |||
35 | @Override | ||
36 | public void apply(ModelStoreBuilder storeBuilder) { | ||
37 | var translator = PartialRelationTranslator.of(partialRelation) | ||
38 | .interpretation(((adapter, concreteness, partialSymbol) -> | ||
39 | adapter.getPartialInterpretation(concreteness, targetRelation))) | ||
40 | .rewriter(this); | ||
41 | if (mutable) { | ||
42 | translator.refiner((adapter, partialSymbol) -> adapter.getRefiner(targetRelation)); | ||
43 | } | ||
44 | storeBuilder.with(translator); | ||
45 | } | ||
46 | |||
47 | @Override | ||
48 | public List<Literal> rewriteLiteral(Set<Variable> positiveVariables, AbstractCallLiteral literal, | ||
49 | Modality modality, Concreteness concreteness) { | ||
50 | return List.of(literal.withTarget(ModalConstraint.of(modality, concreteness, targetRelation))); | ||
51 | } | ||
52 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java new file mode 100644 index 00000000..faf1b958 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class CandidateTypeView extends InferredTypeView { | ||
13 | public CandidateTypeView(Symbol<InferredType> symbol, PartialRelation type) { | ||
14 | super(symbol, "candidate", type); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredType value) { | ||
19 | return type.equals(value.candidateType()); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java deleted file mode 100644 index 6e4728db..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | |||
10 | record EliminatedType(PartialRelation replacement) implements TypeAnalysisResult { | ||
11 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java deleted file mode 100644 index 40de4644..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.query.view.TuplePreservingView; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | import java.util.Objects; | ||
13 | |||
14 | class InferredMayTypeView extends TuplePreservingView<InferredType> { | ||
15 | private final PartialRelation type; | ||
16 | |||
17 | InferredMayTypeView(PartialRelation type) { | ||
18 | super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#may".formatted(type)); | ||
19 | this.type = type; | ||
20 | } | ||
21 | |||
22 | @Override | ||
23 | protected boolean doFilter(Tuple key, InferredType value) { | ||
24 | return value.mayConcreteTypes().contains(type); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean equals(Object o) { | ||
29 | if (this == o) return true; | ||
30 | if (o == null || getClass() != o.getClass()) return false; | ||
31 | if (!super.equals(o)) return false; | ||
32 | InferredMayTypeView that = (InferredMayTypeView) o; | ||
33 | return Objects.equals(type, that.type); | ||
34 | } | ||
35 | |||
36 | @Override | ||
37 | public int hashCode() { | ||
38 | return Objects.hash(super.hashCode(), type); | ||
39 | } | ||
40 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java index fd05158b..9a0c2b0f 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java | |||
@@ -8,21 +8,26 @@ package tools.refinery.store.reasoning.translator.typehierarchy; | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | 9 | ||
10 | import java.util.Collections; | 10 | import java.util.Collections; |
11 | import java.util.Objects; | ||
11 | import java.util.Set; | 12 | import java.util.Set; |
12 | 13 | ||
13 | record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, | 14 | public final class InferredType { |
14 | PartialRelation currentType) { | ||
15 | public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null); | 15 | public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null); |
16 | private final Set<PartialRelation> mustTypes; | ||
17 | private final Set<PartialRelation> mayConcreteTypes; | ||
18 | private final PartialRelation candidateType; | ||
19 | private final int hashCode; | ||
16 | 20 | ||
17 | public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, | 21 | public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, |
18 | PartialRelation currentType) { | 22 | PartialRelation candidateType) { |
19 | this.mustTypes = Collections.unmodifiableSet(mustTypes); | 23 | this.mustTypes = Collections.unmodifiableSet(mustTypes); |
20 | this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes); | 24 | this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes); |
21 | this.currentType = currentType; | 25 | this.candidateType = candidateType; |
26 | hashCode = Objects.hash(mustTypes, mayConcreteTypes, candidateType); | ||
22 | } | 27 | } |
23 | 28 | ||
24 | public boolean isConsistent() { | 29 | public boolean isConsistent() { |
25 | return currentType != null || mustTypes.isEmpty(); | 30 | return candidateType != null || mustTypes.isEmpty(); |
26 | } | 31 | } |
27 | 32 | ||
28 | public boolean isMust(PartialRelation partialRelation) { | 33 | public boolean isMust(PartialRelation partialRelation) { |
@@ -32,4 +37,40 @@ record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConc | |||
32 | public boolean isMayConcrete(PartialRelation partialRelation) { | 37 | public boolean isMayConcrete(PartialRelation partialRelation) { |
33 | return mayConcreteTypes.contains(partialRelation); | 38 | return mayConcreteTypes.contains(partialRelation); |
34 | } | 39 | } |
40 | |||
41 | |||
42 | public Set<PartialRelation> mustTypes() { | ||
43 | return mustTypes; | ||
44 | } | ||
45 | |||
46 | public Set<PartialRelation> mayConcreteTypes() { | ||
47 | return mayConcreteTypes; | ||
48 | } | ||
49 | |||
50 | public PartialRelation candidateType() { | ||
51 | return candidateType; | ||
52 | } | ||
53 | |||
54 | @Override | ||
55 | public boolean equals(Object o) { | ||
56 | if (this == o) return true; | ||
57 | if (o == null || getClass() != o.getClass()) return false; | ||
58 | InferredType that = (InferredType) o; | ||
59 | return Objects.equals(mustTypes, that.mustTypes) && | ||
60 | Objects.equals(mayConcreteTypes, that.mayConcreteTypes) && | ||
61 | Objects.equals(candidateType, that.candidateType); | ||
62 | } | ||
63 | |||
64 | @Override | ||
65 | public int hashCode() { | ||
66 | return hashCode; | ||
67 | } | ||
68 | |||
69 | @Override | ||
70 | public String toString() { | ||
71 | return "InferredType[" + | ||
72 | "mustTypes=" + mustTypes + ", " + | ||
73 | "mayConcreteTypes=" + mayConcreteTypes + ", " + | ||
74 | "candidateType=" + candidateType + ']'; | ||
75 | } | ||
35 | } | 76 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java new file mode 100644 index 00000000..40a7b3fa --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
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.tuple.Tuple; | ||
15 | |||
16 | class InferredTypeRefiner extends AbstractPartialInterpretationRefiner<TruthValue, Boolean> { | ||
17 | private final Interpretation<InferredType> interpretation; | ||
18 | private final TypeAnalysisResult result; | ||
19 | |||
20 | private InferredTypeRefiner(ReasoningAdapter adapter, PartialSymbol<TruthValue, Boolean> partialSymbol, | ||
21 | Symbol<InferredType> symbol, TypeAnalysisResult result) { | ||
22 | super(adapter, partialSymbol); | ||
23 | interpretation = adapter.getModel().getInterpretation(symbol); | ||
24 | this.result = result; | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean merge(Tuple key, TruthValue value) { | ||
29 | var currentType = interpretation.get(key); | ||
30 | var newType = result.merge(currentType, value); | ||
31 | interpretation.put(key, newType); | ||
32 | return true; | ||
33 | } | ||
34 | |||
35 | public static Factory<TruthValue, Boolean> of(Symbol<InferredType> symbol, TypeAnalysisResult result) { | ||
36 | return (adapter, partialSymbol) -> new InferredTypeRefiner(adapter, partialSymbol, symbol, result); | ||
37 | } | ||
38 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java index 1a121547..3c074df5 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java | |||
@@ -1,35 +1,30 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.query.view.TuplePreservingView; | 8 | import tools.refinery.store.query.view.TuplePreservingView; |
10 | import tools.refinery.store.tuple.Tuple; | 9 | import tools.refinery.store.reasoning.representation.PartialRelation; |
10 | import tools.refinery.store.representation.Symbol; | ||
11 | 11 | ||
12 | import java.util.Objects; | 12 | import java.util.Objects; |
13 | 13 | ||
14 | class InferredMustTypeView extends TuplePreservingView<InferredType> { | 14 | abstract class InferredTypeView extends TuplePreservingView<InferredType> { |
15 | private final PartialRelation type; | 15 | protected final PartialRelation type; |
16 | 16 | ||
17 | InferredMustTypeView(PartialRelation type) { | 17 | protected InferredTypeView(Symbol<InferredType> symbol, String name, PartialRelation type) { |
18 | super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#must".formatted(type)); | 18 | super(symbol, type.name() + "#" + name); |
19 | this.type = type; | 19 | this.type = type; |
20 | } | 20 | } |
21 | 21 | ||
22 | @Override | 22 | @Override |
23 | protected boolean doFilter(Tuple key, InferredType value) { | ||
24 | return value.mustTypes().contains(type); | ||
25 | } | ||
26 | |||
27 | @Override | ||
28 | public boolean equals(Object o) { | 23 | public boolean equals(Object o) { |
29 | if (this == o) return true; | 24 | if (this == o) return true; |
30 | if (o == null || getClass() != o.getClass()) return false; | 25 | if (o == null || getClass() != o.getClass()) return false; |
31 | if (!super.equals(o)) return false; | 26 | if (!super.equals(o)) return false; |
32 | InferredMustTypeView that = (InferredMustTypeView) o; | 27 | InferredTypeView that = (InferredTypeView) o; |
33 | return Objects.equals(type, that.type); | 28 | return Objects.equals(type, that.type); |
34 | } | 29 | } |
35 | 30 | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java new file mode 100644 index 00000000..dcaf61c5 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class MayTypeView extends InferredTypeView { | ||
13 | public MayTypeView(Symbol<InferredType> symbol, PartialRelation type) { | ||
14 | super(symbol, "may", type); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredType value) { | ||
19 | return value.mayConcreteTypes().contains(type); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java new file mode 100644 index 00000000..833e1594 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.Symbol; | ||
10 | import tools.refinery.store.tuple.Tuple; | ||
11 | |||
12 | class MustTypeView extends InferredTypeView { | ||
13 | public MustTypeView(Symbol<InferredType> symbol, PartialRelation type) { | ||
14 | super(symbol, "must", type); | ||
15 | } | ||
16 | |||
17 | @Override | ||
18 | protected boolean doFilter(Tuple key, InferredType value) { | ||
19 | return value.mustTypes().contains(type); | ||
20 | } | ||
21 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java deleted file mode 100644 index 0696f4c3..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java +++ /dev/null | |||
@@ -1,141 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | final class PreservedType implements TypeAnalysisResult { | ||
14 | private final ExtendedTypeInfo extendedTypeInfo; | ||
15 | private final List<PartialRelation> directSubtypes; | ||
16 | private final List<ExtendedTypeInfo> allExternalTypeInfoList; | ||
17 | private final InferredType inferredType; | ||
18 | |||
19 | public PreservedType(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) { | ||
20 | this.extendedTypeInfo = extendedTypeInfo; | ||
21 | directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes()); | ||
22 | this.allExternalTypeInfoList = allExternalTypeInfoList; | ||
23 | inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(), | ||
24 | extendedTypeInfo.getConcreteSubtypesAndSelf()); | ||
25 | } | ||
26 | |||
27 | public PartialRelation type() { | ||
28 | return extendedTypeInfo.getType(); | ||
29 | } | ||
30 | |||
31 | public List<PartialRelation> getDirectSubtypes() { | ||
32 | return directSubtypes; | ||
33 | } | ||
34 | |||
35 | public boolean isAbstractType() { | ||
36 | return extendedTypeInfo.isAbstractType(); | ||
37 | } | ||
38 | |||
39 | public boolean isVacuous() { | ||
40 | return isAbstractType() && directSubtypes.isEmpty(); | ||
41 | } | ||
42 | |||
43 | public InferredType asInferredType() { | ||
44 | return inferredType; | ||
45 | } | ||
46 | |||
47 | public InferredType merge(InferredType inferredType, TruthValue value) { | ||
48 | return switch (value) { | ||
49 | case UNKNOWN -> inferredType; | ||
50 | case TRUE -> addMust(inferredType); | ||
51 | case FALSE -> removeMay(inferredType); | ||
52 | case ERROR -> addError(inferredType); | ||
53 | }; | ||
54 | } | ||
55 | |||
56 | private InferredType addMust(InferredType inferredType) { | ||
57 | var originalMustTypes = inferredType.mustTypes(); | ||
58 | if (originalMustTypes.contains(type())) { | ||
59 | return inferredType; | ||
60 | } | ||
61 | var mustTypes = new HashSet<>(originalMustTypes); | ||
62 | extendedTypeInfo.addMust(mustTypes); | ||
63 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
64 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
65 | Set<PartialRelation> mayConcreteTypesResult; | ||
66 | if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
67 | mayConcreteTypesResult = mayConcreteTypes; | ||
68 | } else { | ||
69 | mayConcreteTypesResult = originalMayConcreteTypes; | ||
70 | } | ||
71 | return propagateMust(mustTypes, mayConcreteTypesResult); | ||
72 | } | ||
73 | |||
74 | private InferredType removeMay(InferredType inferredType) { | ||
75 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
76 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
77 | if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
78 | return inferredType; | ||
79 | } | ||
80 | return propagateMust(inferredType.mustTypes(), mayConcreteTypes); | ||
81 | } | ||
82 | |||
83 | private InferredType addError(InferredType inferredType) { | ||
84 | var originalMustTypes = inferredType.mustTypes(); | ||
85 | if (originalMustTypes.contains(type())) { | ||
86 | if (inferredType.mayConcreteTypes().isEmpty()) { | ||
87 | return inferredType; | ||
88 | } | ||
89 | return new InferredType(originalMustTypes, Set.of(), null); | ||
90 | } | ||
91 | var mustTypes = new HashSet<>(originalMustTypes); | ||
92 | extendedTypeInfo.addMust(mustTypes); | ||
93 | return new InferredType(mustTypes, Set.of(), null); | ||
94 | } | ||
95 | |||
96 | private InferredType propagateMust(Set<PartialRelation> originalMustTypes, | ||
97 | Set<PartialRelation> mayConcreteTypes) { | ||
98 | // It is possible that there is not type at all, do not force one by propagation. | ||
99 | var maybeUntyped = originalMustTypes.isEmpty(); | ||
100 | // Para-consistent case, do not propagate must types to avoid logical explosion. | ||
101 | var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty(); | ||
102 | if (maybeUntyped || paraConsistentOrSurelyUntyped) { | ||
103 | return new InferredType(originalMustTypes, mayConcreteTypes, null); | ||
104 | } | ||
105 | var currentType = computeCurrentType(mayConcreteTypes); | ||
106 | var mustTypes = new HashSet<>(originalMustTypes); | ||
107 | boolean changed = false; | ||
108 | for (var newMustExtendedTypeInfo : allExternalTypeInfoList) { | ||
109 | var newMustType = newMustExtendedTypeInfo.getType(); | ||
110 | if (mustTypes.contains(newMustType)) { | ||
111 | continue; | ||
112 | } | ||
113 | if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) { | ||
114 | newMustExtendedTypeInfo.addMust(mustTypes); | ||
115 | changed = true; | ||
116 | } | ||
117 | } | ||
118 | if (!changed) { | ||
119 | return new InferredType(originalMustTypes, mayConcreteTypes, currentType); | ||
120 | } | ||
121 | return new InferredType(mustTypes, mayConcreteTypes, currentType); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types. | ||
126 | * | ||
127 | * @param mayConcreteTypes The set of allowed concrete types. Must not be empty. | ||
128 | * @return The first concrete type that is allowed by {@code matConcreteTypes}. | ||
129 | */ | ||
130 | private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) { | ||
131 | for (var concreteExtendedTypeInfo : allExternalTypeInfoList) { | ||
132 | var concreteType = concreteExtendedTypeInfo.getType(); | ||
133 | if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) { | ||
134 | return concreteType; | ||
135 | } | ||
136 | } | ||
137 | // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>, | ||
138 | // Set<PartialRelation>}. | ||
139 | throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes)); | ||
140 | } | ||
141 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java index fbf8a7c9..ebe0d1b9 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java | |||
@@ -5,5 +5,141 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | sealed interface TypeAnalysisResult permits EliminatedType, PreservedType { | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.representation.TruthValue; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | public final class TypeAnalysisResult { | ||
14 | private final ExtendedTypeInfo extendedTypeInfo; | ||
15 | private final List<PartialRelation> directSubtypes; | ||
16 | private final List<ExtendedTypeInfo> allExternalTypeInfoList; | ||
17 | private final InferredType inferredType; | ||
18 | |||
19 | public TypeAnalysisResult(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) { | ||
20 | this.extendedTypeInfo = extendedTypeInfo; | ||
21 | directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes()); | ||
22 | this.allExternalTypeInfoList = allExternalTypeInfoList; | ||
23 | inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(), | ||
24 | extendedTypeInfo.getConcreteSubtypesAndSelf()); | ||
25 | } | ||
26 | |||
27 | public PartialRelation type() { | ||
28 | return extendedTypeInfo.getType(); | ||
29 | } | ||
30 | |||
31 | public List<PartialRelation> getDirectSubtypes() { | ||
32 | return directSubtypes; | ||
33 | } | ||
34 | |||
35 | public boolean isAbstractType() { | ||
36 | return extendedTypeInfo.isAbstractType(); | ||
37 | } | ||
38 | |||
39 | public boolean isVacuous() { | ||
40 | return isAbstractType() && directSubtypes.isEmpty(); | ||
41 | } | ||
42 | |||
43 | public InferredType asInferredType() { | ||
44 | return inferredType; | ||
45 | } | ||
46 | |||
47 | public boolean isSubtypeOf(TypeAnalysisResult other) { | ||
48 | return extendedTypeInfo.getAllSubtypes().contains(other.type()); | ||
49 | } | ||
50 | |||
51 | public InferredType merge(InferredType inferredType, TruthValue value) { | ||
52 | return switch (value) { | ||
53 | case UNKNOWN -> inferredType; | ||
54 | case TRUE -> addMust(inferredType); | ||
55 | case FALSE -> removeMay(inferredType); | ||
56 | case ERROR -> addError(inferredType); | ||
57 | }; | ||
58 | } | ||
59 | |||
60 | private InferredType addMust(InferredType inferredType) { | ||
61 | var originalMustTypes = inferredType.mustTypes(); | ||
62 | if (originalMustTypes.contains(type())) { | ||
63 | return inferredType; | ||
64 | } | ||
65 | var mustTypes = new HashSet<>(originalMustTypes); | ||
66 | extendedTypeInfo.addMust(mustTypes); | ||
67 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
68 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
69 | Set<PartialRelation> mayConcreteTypesResult; | ||
70 | if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
71 | mayConcreteTypesResult = mayConcreteTypes; | ||
72 | } else { | ||
73 | mayConcreteTypesResult = originalMayConcreteTypes; | ||
74 | } | ||
75 | return propagateMust(mustTypes, mayConcreteTypesResult); | ||
76 | } | ||
77 | |||
78 | private InferredType removeMay(InferredType inferredType) { | ||
79 | var originalMayConcreteTypes = inferredType.mayConcreteTypes(); | ||
80 | var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes); | ||
81 | if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) { | ||
82 | return inferredType; | ||
83 | } | ||
84 | return propagateMust(inferredType.mustTypes(), mayConcreteTypes); | ||
85 | } | ||
86 | |||
87 | private InferredType addError(InferredType inferredType) { | ||
88 | var originalMustTypes = inferredType.mustTypes(); | ||
89 | if (originalMustTypes.contains(type())) { | ||
90 | if (inferredType.mayConcreteTypes().isEmpty()) { | ||
91 | return inferredType; | ||
92 | } | ||
93 | return new InferredType(originalMustTypes, Set.of(), null); | ||
94 | } | ||
95 | var mustTypes = new HashSet<>(originalMustTypes); | ||
96 | extendedTypeInfo.addMust(mustTypes); | ||
97 | return new InferredType(mustTypes, Set.of(), null); | ||
98 | } | ||
99 | |||
100 | private InferredType propagateMust(Set<PartialRelation> originalMustTypes, | ||
101 | Set<PartialRelation> mayConcreteTypes) { | ||
102 | // It is possible that there is not type at all, do not force one by propagation. | ||
103 | var maybeUntyped = originalMustTypes.isEmpty(); | ||
104 | // Para-consistent case, do not propagate must types to avoid logical explosion. | ||
105 | var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty(); | ||
106 | if (maybeUntyped || paraConsistentOrSurelyUntyped) { | ||
107 | return new InferredType(originalMustTypes, mayConcreteTypes, null); | ||
108 | } | ||
109 | var currentType = computeCurrentType(mayConcreteTypes); | ||
110 | var mustTypes = new HashSet<>(originalMustTypes); | ||
111 | boolean changed = false; | ||
112 | for (var newMustExtendedTypeInfo : allExternalTypeInfoList) { | ||
113 | var newMustType = newMustExtendedTypeInfo.getType(); | ||
114 | if (mustTypes.contains(newMustType)) { | ||
115 | continue; | ||
116 | } | ||
117 | if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) { | ||
118 | newMustExtendedTypeInfo.addMust(mustTypes); | ||
119 | changed = true; | ||
120 | } | ||
121 | } | ||
122 | if (!changed) { | ||
123 | return new InferredType(originalMustTypes, mayConcreteTypes, currentType); | ||
124 | } | ||
125 | return new InferredType(mustTypes, mayConcreteTypes, currentType); | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types. | ||
130 | * | ||
131 | * @param mayConcreteTypes The set of allowed concrete types. Must not be empty. | ||
132 | * @return The first concrete type that is allowed by {@code matConcreteTypes}. | ||
133 | */ | ||
134 | private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) { | ||
135 | for (var concreteExtendedTypeInfo : allExternalTypeInfoList) { | ||
136 | var concreteType = concreteExtendedTypeInfo.getType(); | ||
137 | if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) { | ||
138 | return concreteType; | ||
139 | } | ||
140 | } | ||
141 | // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>, | ||
142 | // Set<PartialRelation>}. | ||
143 | throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes)); | ||
144 | } | ||
9 | } | 145 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java index e97ce954..3f918c97 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java | |||
@@ -6,17 +6,20 @@ | |||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | 8 | import tools.refinery.store.reasoning.representation.PartialRelation; |
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
9 | 10 | ||
10 | import java.util.*; | 11 | import java.util.*; |
11 | 12 | ||
12 | class TypeAnalyzer { | 13 | public class TypeHierarchy { |
14 | private final Set<PartialRelation> allTypes; | ||
13 | private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap; | 15 | private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap; |
14 | private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>(); | 16 | private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>(); |
15 | private final InferredType unknownType; | 17 | private final InferredType unknownType; |
16 | private final Map<PartialRelation, TypeAnalysisResult> analysisResults; | 18 | private final Map<PartialRelation, TypeAnalysisResult> preservedTypes; |
17 | 19 | ||
18 | public TypeAnalyzer(Map<PartialRelation, TypeInfo> typeInfoMap) { | 20 | TypeHierarchy(Map<PartialRelation, TypeInfo> typeInfoMap) { |
19 | int size = typeInfoMap.size(); | 21 | int size = typeInfoMap.size(); |
22 | allTypes = Collections.unmodifiableSet(new LinkedHashSet<>(typeInfoMap.keySet())); | ||
20 | extendedTypeInfoMap = new LinkedHashMap<>(size); | 23 | extendedTypeInfoMap = new LinkedHashMap<>(size); |
21 | var concreteTypes = new LinkedHashSet<PartialRelation>(); | 24 | var concreteTypes = new LinkedHashSet<PartialRelation>(); |
22 | int index = 0; | 25 | int index = 0; |
@@ -34,15 +37,39 @@ class TypeAnalyzer { | |||
34 | computeAllAndConcreteSubtypes(); | 37 | computeAllAndConcreteSubtypes(); |
35 | computeDirectSubtypes(); | 38 | computeDirectSubtypes(); |
36 | eliminateTrivialSupertypes(); | 39 | eliminateTrivialSupertypes(); |
37 | analysisResults = computeAnalysisResults(); | 40 | preservedTypes = computeAnalysisResults(); |
41 | } | ||
42 | |||
43 | public boolean isEmpty() { | ||
44 | return extendedTypeInfoMap.isEmpty(); | ||
38 | } | 45 | } |
39 | 46 | ||
40 | public InferredType getUnknownType() { | 47 | public InferredType getUnknownType() { |
41 | return unknownType; | 48 | return unknownType; |
42 | } | 49 | } |
43 | 50 | ||
44 | public Map<PartialRelation, TypeAnalysisResult> getAnalysisResults() { | 51 | public Set<PartialRelation> getAllTypes() { |
45 | return analysisResults; | 52 | return allTypes; |
53 | } | ||
54 | |||
55 | public Map<PartialRelation, TypeAnalysisResult> getPreservedTypes() { | ||
56 | return preservedTypes; | ||
57 | } | ||
58 | |||
59 | public Map<PartialRelation, PartialRelation> getEliminatedTypes() { | ||
60 | return Collections.unmodifiableMap(replacements); | ||
61 | } | ||
62 | |||
63 | public TypeAnalysisResult getAnalysisResult(PartialRelation type) { | ||
64 | var preservedResult = preservedTypes.get(type); | ||
65 | if (preservedResult != null) { | ||
66 | return preservedResult; | ||
67 | } | ||
68 | var eliminatedResult = replacements.get(type); | ||
69 | if (eliminatedResult != null) { | ||
70 | return preservedTypes.get(eliminatedResult); | ||
71 | } | ||
72 | throw new IllegalArgumentException("Unknown type: " + type); | ||
46 | } | 73 | } |
47 | 74 | ||
48 | private void computeAllSupertypes() { | 75 | private void computeAllSupertypes() { |
@@ -53,7 +80,13 @@ class TypeAnalyzer { | |||
53 | var found = new HashSet<PartialRelation>(); | 80 | var found = new HashSet<PartialRelation>(); |
54 | var allSupertypes = extendedTypeInfo.getAllSupertypes(); | 81 | var allSupertypes = extendedTypeInfo.getAllSupertypes(); |
55 | for (var supertype : allSupertypes) { | 82 | for (var supertype : allSupertypes) { |
56 | found.addAll(extendedTypeInfoMap.get(supertype).getAllSupertypes()); | 83 | var supertypeInfo = extendedTypeInfoMap.get(supertype); |
84 | if (supertypeInfo == null) { | ||
85 | throw new TranslationException(extendedTypeInfo.getType(), | ||
86 | "Supertype %s of %s is missing from the type hierarchy" | ||
87 | .formatted(supertype, extendedTypeInfo.getType())); | ||
88 | } | ||
89 | found.addAll(supertypeInfo.getAllSupertypes()); | ||
57 | } | 90 | } |
58 | if (allSupertypes.addAll(found)) { | 91 | if (allSupertypes.addAll(found)) { |
59 | changed = true; | 92 | changed = true; |
@@ -70,7 +103,7 @@ class TypeAnalyzer { | |||
70 | } | 103 | } |
71 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | 104 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { |
72 | if (type.equals(supertype)) { | 105 | if (type.equals(supertype)) { |
73 | throw new IllegalArgumentException("%s cannot be a supertype of itself".formatted(type)); | 106 | throw new TranslationException(type, "%s cannot be a supertype of itself".formatted(type)); |
74 | } | 107 | } |
75 | var supertypeInfo = extendedTypeInfoMap.get(supertype); | 108 | var supertypeInfo = extendedTypeInfoMap.get(supertype); |
76 | supertypeInfo.getAllSubtypes().add(type); | 109 | supertypeInfo.getAllSubtypes().add(type); |
@@ -95,25 +128,37 @@ class TypeAnalyzer { | |||
95 | } | 128 | } |
96 | 129 | ||
97 | private void eliminateTrivialSupertypes() { | 130 | private void eliminateTrivialSupertypes() { |
98 | boolean changed; | 131 | Set<PartialRelation> toInspect = new HashSet<>(extendedTypeInfoMap.keySet()); |
99 | do { | 132 | while (!toInspect.isEmpty()) { |
100 | var toRemove = new ArrayList<PartialRelation>(); | 133 | var toRemove = new ArrayList<PartialRelation>(); |
101 | for (var entry : extendedTypeInfoMap.entrySet()) { | 134 | for (var partialRelation : toInspect) { |
102 | var extendedTypeInfo = entry.getValue(); | 135 | var extendedTypeInfo = extendedTypeInfoMap.get(partialRelation); |
103 | boolean isAbstract = extendedTypeInfo.isAbstractType(); | 136 | if (extendedTypeInfo != null && isTrivialSupertype(extendedTypeInfo)) { |
104 | // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e., | 137 | toRemove.add(partialRelation); |
105 | // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist. | ||
106 | boolean hasSingleDirectSubtype = extendedTypeInfo.getDirectSubtypes().size() == 1; | ||
107 | if (isAbstract && hasSingleDirectSubtype) { | ||
108 | toRemove.add(entry.getKey()); | ||
109 | } | 138 | } |
110 | } | 139 | } |
111 | toRemove.forEach(this::removeTrivialType); | 140 | toInspect.clear(); |
112 | changed = !toRemove.isEmpty(); | 141 | for (var partialRelation : toRemove) { |
113 | } while (changed); | 142 | removeTrivialType(partialRelation, toInspect); |
143 | } | ||
144 | } | ||
145 | } | ||
146 | |||
147 | private boolean isTrivialSupertype(ExtendedTypeInfo extendedTypeInfo) { | ||
148 | if (!extendedTypeInfo.isAbstractType()) { | ||
149 | return false; | ||
150 | } | ||
151 | var subtypeIterator = extendedTypeInfo.getDirectSubtypes().iterator(); | ||
152 | if (!subtypeIterator.hasNext()) { | ||
153 | // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e., | ||
154 | // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist. | ||
155 | return false; | ||
156 | } | ||
157 | var directSubtype = subtypeIterator.next(); | ||
158 | return !extendedTypeInfoMap.get(directSubtype).isAbstractType() && !subtypeIterator.hasNext(); | ||
114 | } | 159 | } |
115 | 160 | ||
116 | private void removeTrivialType(PartialRelation trivialType) { | 161 | private void removeTrivialType(PartialRelation trivialType, Set<PartialRelation> toInspect) { |
117 | var extendedTypeInfo = extendedTypeInfoMap.get(trivialType); | 162 | var extendedTypeInfo = extendedTypeInfoMap.get(trivialType); |
118 | var iterator = extendedTypeInfo.getDirectSubtypes().iterator(); | 163 | var iterator = extendedTypeInfo.getDirectSubtypes().iterator(); |
119 | if (!iterator.hasNext()) { | 164 | if (!iterator.hasNext()) { |
@@ -125,7 +170,6 @@ class TypeAnalyzer { | |||
125 | throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype" | 170 | throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype" |
126 | .formatted(trivialType)); | 171 | .formatted(trivialType)); |
127 | } | 172 | } |
128 | replacements.put(trivialType, replacement); | ||
129 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { | 173 | for (var supertype : extendedTypeInfo.getAllSupertypes()) { |
130 | var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype); | 174 | var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype); |
131 | if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) { | 175 | if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) { |
@@ -134,6 +178,9 @@ class TypeAnalyzer { | |||
134 | var directSubtypes = extendedSupertypeInfo.getDirectSubtypes(); | 178 | var directSubtypes = extendedSupertypeInfo.getDirectSubtypes(); |
135 | if (directSubtypes.remove(trivialType)) { | 179 | if (directSubtypes.remove(trivialType)) { |
136 | directSubtypes.add(replacement); | 180 | directSubtypes.add(replacement); |
181 | if (extendedSupertypeInfo.isAbstractType() && directSubtypes.size() == 1) { | ||
182 | toInspect.add(supertype); | ||
183 | } | ||
137 | } | 184 | } |
138 | } | 185 | } |
139 | for (var subtype : extendedTypeInfo.getAllSubtypes()) { | 186 | for (var subtype : extendedTypeInfo.getAllSubtypes()) { |
@@ -156,17 +203,13 @@ class TypeAnalyzer { | |||
156 | 203 | ||
157 | private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() { | 204 | private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() { |
158 | var allExtendedTypeInfoList = sortTypes(); | 205 | var allExtendedTypeInfoList = sortTypes(); |
159 | var results = new LinkedHashMap<PartialRelation, TypeAnalysisResult>( | 206 | var preservedResults = new LinkedHashMap<PartialRelation, TypeAnalysisResult>( |
160 | allExtendedTypeInfoList.size() + replacements.size()); | 207 | allExtendedTypeInfoList.size()); |
161 | for (var extendedTypeInfo : allExtendedTypeInfoList) { | 208 | for (var extendedTypeInfo : allExtendedTypeInfoList) { |
162 | var type = extendedTypeInfo.getType(); | 209 | var type = extendedTypeInfo.getType(); |
163 | results.put(type, new PreservedType(extendedTypeInfo, allExtendedTypeInfoList)); | 210 | preservedResults.put(type, new TypeAnalysisResult(extendedTypeInfo, allExtendedTypeInfoList)); |
164 | } | ||
165 | for (var entry : replacements.entrySet()) { | ||
166 | var type = entry.getKey(); | ||
167 | results.put(type, new EliminatedType(entry.getValue())); | ||
168 | } | 211 | } |
169 | return Collections.unmodifiableMap(results); | 212 | return Collections.unmodifiableMap(preservedResults); |
170 | } | 213 | } |
171 | 214 | ||
172 | private List<ExtendedTypeInfo> sortTypes() { | 215 | private List<ExtendedTypeInfo> sortTypes() { |
@@ -204,4 +247,8 @@ class TypeAnalyzer { | |||
204 | } | 247 | } |
205 | return Collections.unmodifiableList(sorted); | 248 | return Collections.unmodifiableList(sorted); |
206 | } | 249 | } |
250 | |||
251 | public static TypeHierarchyBuilder builder() { | ||
252 | return new TypeHierarchyBuilder(); | ||
253 | } | ||
207 | } | 254 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java new file mode 100644 index 00000000..ce8fda05 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java | |||
@@ -0,0 +1,66 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
9 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
10 | |||
11 | import java.util.*; | ||
12 | |||
13 | @SuppressWarnings("UnusedReturnValue") | ||
14 | public class TypeHierarchyBuilder { | ||
15 | protected final Map<PartialRelation, TypeInfo> typeInfoMap = new LinkedHashMap<>(); | ||
16 | |||
17 | protected TypeHierarchyBuilder() { | ||
18 | } | ||
19 | |||
20 | public TypeHierarchyBuilder type(PartialRelation partialRelation, TypeInfo typeInfo) { | ||
21 | if (partialRelation.arity() != 1) { | ||
22 | throw new TranslationException(partialRelation, | ||
23 | "Only types of arity 1 are supported, got %s with %d instead" | ||
24 | .formatted(partialRelation, partialRelation.arity())); | ||
25 | } | ||
26 | var putResult = typeInfoMap.put(partialRelation, typeInfo); | ||
27 | if (putResult != null && !putResult.equals(typeInfo)) { | ||
28 | throw new TranslationException(partialRelation, | ||
29 | "Duplicate type info for partial relation: " + partialRelation); | ||
30 | } | ||
31 | return this; | ||
32 | } | ||
33 | |||
34 | public TypeHierarchyBuilder type(PartialRelation partialRelation, boolean abstractType, | ||
35 | PartialRelation... supertypes) { | ||
36 | return type(partialRelation, abstractType, Set.of(supertypes)); | ||
37 | } | ||
38 | |||
39 | public TypeHierarchyBuilder type(PartialRelation partialRelation, boolean abstractType, | ||
40 | Collection<PartialRelation> supertypes) { | ||
41 | return type(partialRelation, new TypeInfo(supertypes, abstractType)); | ||
42 | } | ||
43 | |||
44 | public TypeHierarchyBuilder type(PartialRelation partialRelation, PartialRelation... supertypes) { | ||
45 | return type(partialRelation, List.of(supertypes)); | ||
46 | } | ||
47 | |||
48 | public TypeHierarchyBuilder type(PartialRelation partialRelation, Collection<PartialRelation> supertypes) { | ||
49 | return type(partialRelation, false, supertypes); | ||
50 | } | ||
51 | |||
52 | public TypeHierarchyBuilder types(Collection<Map.Entry<PartialRelation, TypeInfo>> entries) { | ||
53 | for (var entry : entries) { | ||
54 | type(entry.getKey(), entry.getValue()); | ||
55 | } | ||
56 | return this; | ||
57 | } | ||
58 | |||
59 | public TypeHierarchyBuilder types(Map<PartialRelation, TypeInfo> map) { | ||
60 | return types(map.entrySet()); | ||
61 | } | ||
62 | |||
63 | public TypeHierarchy build() { | ||
64 | return new TypeHierarchy(typeInfoMap); | ||
65 | } | ||
66 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java new file mode 100644 index 00000000..233e43f0 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.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.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.refinement.PartialModelInitializer; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | import tools.refinery.store.representation.TruthValue; | ||
14 | import tools.refinery.store.tuple.Tuple; | ||
15 | |||
16 | import java.util.Arrays; | ||
17 | import java.util.HashMap; | ||
18 | import java.util.function.Function; | ||
19 | |||
20 | public class TypeHierarchyInitializer implements PartialModelInitializer { | ||
21 | private final TypeHierarchy typeHierarchy; | ||
22 | private final Symbol<InferredType> typeSymbol; | ||
23 | |||
24 | public TypeHierarchyInitializer(TypeHierarchy typeHierarchy, Symbol<InferredType> typeSymbol) { | ||
25 | this.typeHierarchy = typeHierarchy; | ||
26 | this.typeSymbol = typeSymbol; | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public void initialize(Model model, ModelSeed modelSeed) { | ||
31 | var inferredTypes = new InferredType[modelSeed.getNodeCount()]; | ||
32 | Arrays.fill(inferredTypes, typeHierarchy.getUnknownType()); | ||
33 | for (var type : typeHierarchy.getAllTypes()) { | ||
34 | model.checkCancelled(); | ||
35 | initializeType(type, inferredTypes, model, modelSeed); | ||
36 | } | ||
37 | var typeInterpretation = model.getInterpretation(typeSymbol); | ||
38 | var uniqueTable = new HashMap<InferredType, InferredType>(); | ||
39 | for (int i = 0; i < inferredTypes.length; i++) { | ||
40 | model.checkCancelled(); | ||
41 | var uniqueType = uniqueTable.computeIfAbsent(inferredTypes[i], Function.identity()); | ||
42 | typeInterpretation.put(Tuple.of(i), uniqueType); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | private void initializeType(PartialRelation type, InferredType[] inferredTypes, Model model, ModelSeed modelSeed) { | ||
47 | var cursor = modelSeed.getCursor(type, TruthValue.UNKNOWN); | ||
48 | var analysisResult = typeHierarchy.getAnalysisResult(type); | ||
49 | while (cursor.move()) { | ||
50 | model.checkCancelled(); | ||
51 | var i = cursor.getKey().get(0); | ||
52 | checkNodeId(inferredTypes, i); | ||
53 | var value = cursor.getValue(); | ||
54 | inferredTypes[i] = analysisResult.merge(inferredTypes[i], value); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | private void checkNodeId(InferredType[] inferredTypes, int nodeId) { | ||
59 | if (nodeId < 0 || nodeId >= inferredTypes.length) { | ||
60 | throw new IllegalArgumentException("Expected node id %d to be lower than model size %d" | ||
61 | .formatted(nodeId, inferredTypes.length)); | ||
62 | } | ||
63 | } | ||
64 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java deleted file mode 100644 index 06e3c05f..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.model.Model; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.translator.TranslatedRelation; | ||
11 | import tools.refinery.store.reasoning.translator.TranslationUnit; | ||
12 | import tools.refinery.store.representation.Symbol; | ||
13 | |||
14 | import java.util.Collection; | ||
15 | import java.util.List; | ||
16 | import java.util.Map; | ||
17 | |||
18 | public class TypeHierarchyTranslationUnit extends TranslationUnit { | ||
19 | static final Symbol<InferredType> INFERRED_TYPE_SYMBOL = Symbol.of( | ||
20 | "inferredType", 1, InferredType.class, InferredType.UNTYPED); | ||
21 | |||
22 | private final TypeAnalyzer typeAnalyzer; | ||
23 | |||
24 | public TypeHierarchyTranslationUnit(Map<PartialRelation, TypeInfo> typeInfoMap) { | ||
25 | typeAnalyzer = new TypeAnalyzer(typeInfoMap); | ||
26 | } | ||
27 | |||
28 | @Override | ||
29 | public Collection<TranslatedRelation> getTranslatedRelations() { | ||
30 | return List.of(); | ||
31 | } | ||
32 | |||
33 | @Override | ||
34 | public void initializeModel(Model model, int nodeCount) { | ||
35 | |||
36 | } | ||
37 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java new file mode 100644 index 00000000..37ea1448 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java | |||
@@ -0,0 +1,111 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import tools.refinery.store.dse.transition.Rule; | ||
9 | import tools.refinery.store.dse.transition.actions.ActionLiteral; | ||
10 | import tools.refinery.store.model.ModelStoreBuilder; | ||
11 | import tools.refinery.store.model.ModelStoreConfiguration; | ||
12 | import tools.refinery.store.query.dnf.Query; | ||
13 | import tools.refinery.store.reasoning.ReasoningBuilder; | ||
14 | import tools.refinery.store.reasoning.actions.PartialActionLiterals; | ||
15 | import tools.refinery.store.reasoning.literal.PartialLiterals; | ||
16 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
17 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
18 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
19 | import tools.refinery.store.reasoning.translator.proxy.PartialRelationTranslatorProxy; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | |||
22 | import java.util.ArrayList; | ||
23 | |||
24 | import static tools.refinery.store.query.literal.Literals.not; | ||
25 | import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust; | ||
26 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | ||
27 | |||
28 | public class TypeHierarchyTranslator implements ModelStoreConfiguration { | ||
29 | public static final Symbol<InferredType> TYPE_SYMBOL = Symbol.of("TYPE", 1, InferredType.class, | ||
30 | InferredType.UNTYPED); | ||
31 | private final TypeHierarchy typeHierarchy; | ||
32 | |||
33 | public TypeHierarchyTranslator(TypeHierarchy typeHierarchy) { | ||
34 | this.typeHierarchy = typeHierarchy; | ||
35 | } | ||
36 | |||
37 | @Override | ||
38 | public void apply(ModelStoreBuilder storeBuilder) { | ||
39 | if (typeHierarchy.isEmpty()) { | ||
40 | return; | ||
41 | } | ||
42 | |||
43 | storeBuilder.symbol(TYPE_SYMBOL); | ||
44 | |||
45 | for (var entry : typeHierarchy.getPreservedTypes().entrySet()) { | ||
46 | storeBuilder.with(createPreservedTypeTranslator(entry.getKey(), entry.getValue())); | ||
47 | } | ||
48 | |||
49 | for (var entry : typeHierarchy.getEliminatedTypes().entrySet()) { | ||
50 | storeBuilder.with(createEliminatedTypeTranslator(entry.getKey(), entry.getValue())); | ||
51 | } | ||
52 | |||
53 | var reasoningBuilder = storeBuilder.getAdapter(ReasoningBuilder.class); | ||
54 | reasoningBuilder.initializer(new TypeHierarchyInitializer(typeHierarchy, TYPE_SYMBOL)); | ||
55 | } | ||
56 | |||
57 | private ModelStoreConfiguration createPreservedTypeTranslator(PartialRelation type, TypeAnalysisResult result) { | ||
58 | var may = Query.of(type.name() + "#partial#may", (builder, p1) -> { | ||
59 | if (!result.isAbstractType()) { | ||
60 | builder.clause(new MayTypeView(TYPE_SYMBOL, type).call(p1)); | ||
61 | } | ||
62 | for (var subtype : result.getDirectSubtypes()) { | ||
63 | builder.clause(may(subtype.call(p1))); | ||
64 | } | ||
65 | }); | ||
66 | |||
67 | var must = Query.of(type.name() + "#partial#must", (builder, p1) -> builder.clause( | ||
68 | new MustTypeView(TYPE_SYMBOL, type).call(p1) | ||
69 | )); | ||
70 | |||
71 | var candidate = Query.of(type.name() + "#candidate", (builder, p1) -> { | ||
72 | if (!result.isAbstractType()) { | ||
73 | builder.clause(new CandidateTypeView(TYPE_SYMBOL, type).call(p1)); | ||
74 | } | ||
75 | for (var subtype : result.getDirectSubtypes()) { | ||
76 | builder.clause(PartialLiterals.candidateMust(subtype.call(p1))); | ||
77 | } | ||
78 | }); | ||
79 | |||
80 | var translator = PartialRelationTranslator.of(type) | ||
81 | .may(may) | ||
82 | .must(must) | ||
83 | .candidate(candidate) | ||
84 | .refiner(InferredTypeRefiner.of(TYPE_SYMBOL, result)); | ||
85 | |||
86 | if (!result.isAbstractType()) { | ||
87 | var decision = Rule.of(type.name(), (builder, instance) -> builder | ||
88 | .clause( | ||
89 | may(type.call(instance)), | ||
90 | not(candidateMust(type.call(instance))), | ||
91 | not(MultiObjectTranslator.MULTI_VIEW.call(instance)) | ||
92 | ) | ||
93 | .action(() -> { | ||
94 | var actionLiterals = new ArrayList<ActionLiteral>(); | ||
95 | actionLiterals.add(PartialActionLiterals.add(type, instance)); | ||
96 | for (var subtype : result.getDirectSubtypes()) { | ||
97 | actionLiterals.add(PartialActionLiterals.remove(subtype, instance)); | ||
98 | } | ||
99 | return actionLiterals; | ||
100 | })); | ||
101 | translator.decision(decision); | ||
102 | } | ||
103 | |||
104 | return translator; | ||
105 | } | ||
106 | |||
107 | private ModelStoreConfiguration createEliminatedTypeTranslator( | ||
108 | PartialRelation type, PartialRelation replacement) { | ||
109 | return new PartialRelationTranslatorProxy(type, replacement, true); | ||
110 | } | ||
111 | } | ||
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java index 9f897e46..e6bdaff2 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java | |||
@@ -9,43 +9,15 @@ import tools.refinery.store.reasoning.representation.PartialRelation; | |||
9 | 9 | ||
10 | import java.util.*; | 10 | import java.util.*; |
11 | 11 | ||
12 | public record TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) { | 12 | public record TypeInfo(Set<PartialRelation> supertypes, boolean abstractType) { |
13 | public static Builder builder() { | 13 | public TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) { |
14 | return new Builder(); | 14 | this(Set.copyOf(supertypes), abstractType); |
15 | } | 15 | } |
16 | 16 | ||
17 | public static class Builder { | 17 | public TypeInfo addSupertype(PartialRelation newSupertype) { |
18 | private final Set<PartialRelation> supertypes = new LinkedHashSet<>(); | 18 | var newSupertypes = new ArrayList<PartialRelation>(supertypes.size() + 1); |
19 | private boolean abstractType; | 19 | newSupertypes.addAll(supertypes); |
20 | 20 | newSupertypes.add(newSupertype); | |
21 | private Builder() { | 21 | return new TypeInfo(newSupertypes, abstractType); |
22 | } | ||
23 | |||
24 | public Builder supertypes(Collection<PartialRelation> supertypes) { | ||
25 | this.supertypes.addAll(supertypes); | ||
26 | return this; | ||
27 | } | ||
28 | |||
29 | public Builder supertypes(PartialRelation... supertypes) { | ||
30 | return supertypes(List.of(supertypes)); | ||
31 | } | ||
32 | |||
33 | public Builder supertype(PartialRelation supertype) { | ||
34 | supertypes.add(supertype); | ||
35 | return this; | ||
36 | } | ||
37 | |||
38 | public Builder abstractType(boolean abstractType) { | ||
39 | this.abstractType = abstractType; | ||
40 | return this; | ||
41 | } | ||
42 | |||
43 | public Builder abstractType() { | ||
44 | return abstractType(true); | ||
45 | } | ||
46 | |||
47 | public TypeInfo build() { | ||
48 | return new TypeInfo(Collections.unmodifiableSet(supertypes), abstractType); | ||
49 | } | ||
50 | } | 22 | } |
51 | } | 23 | } |
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java new file mode 100644 index 00000000..77560a68 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java | |||
@@ -0,0 +1,108 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.term.Variable; | ||
13 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
14 | import tools.refinery.store.query.view.ForbiddenView; | ||
15 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
16 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
17 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
19 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
20 | import tools.refinery.store.representation.Symbol; | ||
21 | import tools.refinery.store.representation.TruthValue; | ||
22 | import tools.refinery.store.tuple.Tuple; | ||
23 | |||
24 | import static org.hamcrest.MatcherAssert.assertThat; | ||
25 | import static org.hamcrest.Matchers.is; | ||
26 | import static org.hamcrest.Matchers.not; | ||
27 | import static org.hamcrest.Matchers.nullValue; | ||
28 | import static tools.refinery.store.query.literal.Literals.not; | ||
29 | import static tools.refinery.store.reasoning.ReasoningAdapter.EQUALS_SYMBOL; | ||
30 | import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL; | ||
31 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | ||
32 | import static tools.refinery.store.reasoning.literal.PartialLiterals.must; | ||
33 | |||
34 | class PartialModelTest { | ||
35 | @Test | ||
36 | void partialModelTest() { | ||
37 | var person = new PartialRelation("Person", 1); | ||
38 | var friend = new PartialRelation("friend", 2); | ||
39 | var lonely = new PartialRelation("lonely", 1); | ||
40 | |||
41 | var personStorage = Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE); | ||
42 | var friendStorage = Symbol.of("friend", 2, TruthValue.class, TruthValue.UNKNOWN); | ||
43 | |||
44 | var store = ModelStore.builder() | ||
45 | .with(ViatraModelQueryAdapter.builder()) | ||
46 | .with(ReasoningAdapter.builder()) | ||
47 | .with(new MultiObjectTranslator()) | ||
48 | .with(PartialRelationTranslator.of(person) | ||
49 | .symbol(personStorage)) | ||
50 | .with(PartialRelationTranslator.of(friend) | ||
51 | .symbol(friendStorage) | ||
52 | .may(Query.of("mayFriend", (builder, p1, p2) -> builder.clause( | ||
53 | may(person.call(p1)), | ||
54 | may(person.call(p2)), | ||
55 | not(must(EQUALS_SYMBOL.call(p1, p2))), | ||
56 | not(new ForbiddenView(friendStorage).call(p1, p2)) | ||
57 | )))) | ||
58 | .with(PartialRelationTranslator.of(lonely) | ||
59 | .query(Query.of("lonely", (builder, p1) -> builder.clause( | ||
60 | person.call(p1), | ||
61 | not(friend.call(p1, Variable.of()))) | ||
62 | ))) | ||
63 | .build(); | ||
64 | |||
65 | var modelSeed = ModelSeed.builder(4) | ||
66 | .seed(EXISTS_SYMBOL, builder -> builder | ||
67 | .put(Tuple.of(0), TruthValue.TRUE) | ||
68 | .put(Tuple.of(1), TruthValue.UNKNOWN) | ||
69 | .put(Tuple.of(2), TruthValue.TRUE) | ||
70 | .put(Tuple.of(3), TruthValue.TRUE)) | ||
71 | .seed(EQUALS_SYMBOL, builder -> builder | ||
72 | .put(Tuple.of(0, 0), TruthValue.TRUE) | ||
73 | .put(Tuple.of(1, 1), TruthValue.UNKNOWN) | ||
74 | .put(Tuple.of(2, 2), TruthValue.UNKNOWN) | ||
75 | .put(Tuple.of(3, 3), TruthValue.TRUE)) | ||
76 | .seed(person, builder -> builder | ||
77 | .put(Tuple.of(0), TruthValue.TRUE) | ||
78 | .put(Tuple.of(1), TruthValue.TRUE) | ||
79 | .put(Tuple.of(2), TruthValue.UNKNOWN)) | ||
80 | .seed(friend, builder -> builder | ||
81 | .reducedValue(TruthValue.UNKNOWN) | ||
82 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
83 | .put(Tuple.of(1, 2), TruthValue.FALSE)) | ||
84 | .build(); | ||
85 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); | ||
86 | |||
87 | var queryAdapter = model.getAdapter(ModelQueryAdapter.class); | ||
88 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
89 | var friendInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, friend); | ||
90 | var friendRefiner = reasoningAdapter.getRefiner(friend); | ||
91 | |||
92 | assertThat(friendInterpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE)); | ||
93 | assertThat(friendInterpretation.get(Tuple.of(1, 0)), is(TruthValue.UNKNOWN)); | ||
94 | assertThat(friendInterpretation.get(Tuple.of(3, 0)), is(TruthValue.FALSE)); | ||
95 | |||
96 | assertThat(friendRefiner.merge(Tuple.of(0, 1), TruthValue.FALSE), is(true)); | ||
97 | assertThat(friendRefiner.merge(Tuple.of(1, 0), TruthValue.TRUE), is(true)); | ||
98 | var splitResult = reasoningAdapter.split(1); | ||
99 | assertThat(splitResult, not(nullValue())); | ||
100 | var newPerson = splitResult.get(0); | ||
101 | queryAdapter.flushChanges(); | ||
102 | |||
103 | assertThat(friendInterpretation.get(Tuple.of(0, 1)), is(TruthValue.ERROR)); | ||
104 | assertThat(friendInterpretation.get(Tuple.of(1, 0)), is(TruthValue.TRUE)); | ||
105 | assertThat(friendInterpretation.get(Tuple.of(0, newPerson)), is(TruthValue.ERROR)); | ||
106 | assertThat(friendInterpretation.get(Tuple.of(newPerson, 0)), is(TruthValue.TRUE)); | ||
107 | } | ||
108 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java new file mode 100644 index 00000000..793d1cec --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java | |||
@@ -0,0 +1,395 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.lifting; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.query.dnf.Dnf; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.term.ParameterDirection; | ||
13 | import tools.refinery.store.query.view.AnySymbolView; | ||
14 | import tools.refinery.store.query.view.FunctionView; | ||
15 | import tools.refinery.store.query.view.MustView; | ||
16 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
17 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
18 | import tools.refinery.store.reasoning.literal.ModalConstraint; | ||
19 | import tools.refinery.store.reasoning.literal.Modality; | ||
20 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
21 | import tools.refinery.store.reasoning.representation.PartialSymbol; | ||
22 | import tools.refinery.store.representation.Symbol; | ||
23 | import tools.refinery.store.representation.TruthValue; | ||
24 | |||
25 | import java.util.List; | ||
26 | |||
27 | import static org.hamcrest.MatcherAssert.assertThat; | ||
28 | import static tools.refinery.store.query.literal.Literals.check; | ||
29 | import static tools.refinery.store.query.literal.Literals.not; | ||
30 | import static tools.refinery.store.query.term.int_.IntTerms.*; | ||
31 | import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo; | ||
32 | |||
33 | class DnfLifterTest { | ||
34 | private static final Symbol<TruthValue> friendSymbol = Symbol.of("friend", 2, TruthValue.class, | ||
35 | TruthValue.UNKNOWN); | ||
36 | private static final AnySymbolView friendMustView = new MustView(friendSymbol); | ||
37 | private static final Symbol<Integer> age = Symbol.of("age", 1, Integer.class); | ||
38 | private static final FunctionView<Integer> ageView = new FunctionView<>(age); | ||
39 | private static final PartialRelation person = PartialSymbol.of("Person", 1); | ||
40 | private static final PartialRelation friend = PartialSymbol.of("friend", 2); | ||
41 | |||
42 | private DnfLifter sut; | ||
43 | |||
44 | @BeforeEach | ||
45 | void beforeEach() { | ||
46 | sut = new DnfLifter(); | ||
47 | } | ||
48 | |||
49 | @Test | ||
50 | void liftPartialRelationCallTest() { | ||
51 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
52 | friend.call(p1, v1) | ||
53 | ))).getDnf(); | ||
54 | var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input); | ||
55 | |||
56 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
57 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, v1), | ||
58 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1) | ||
59 | ))).getDnf(); | ||
60 | |||
61 | assertThat(actual, structurallyEqualTo(expected)); | ||
62 | } | ||
63 | |||
64 | @Test | ||
65 | void liftPartialDnfCallTest() { | ||
66 | var called = Query.of("Called", (builder, p1, p2) -> builder.clause( | ||
67 | friend.call(p1, p2), | ||
68 | friend.call(p2, p1) | ||
69 | )); | ||
70 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
71 | called.call(p1, v1) | ||
72 | ))).getDnf(); | ||
73 | var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input); | ||
74 | |||
75 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
76 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, called.getDnf()).call(p1, v1), | ||
77 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1) | ||
78 | ))).getDnf(); | ||
79 | |||
80 | assertThat(actual, structurallyEqualTo(expected)); | ||
81 | } | ||
82 | |||
83 | @Test | ||
84 | void liftSymbolViewCallTest() { | ||
85 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
86 | friendMustView.call(p1, v1) | ||
87 | ))).getDnf(); | ||
88 | var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input); | ||
89 | |||
90 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
91 | friendMustView.call(p1, v1), | ||
92 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1) | ||
93 | ))).getDnf(); | ||
94 | |||
95 | assertThat(actual, structurallyEqualTo(expected)); | ||
96 | } | ||
97 | |||
98 | @Test | ||
99 | void liftPartialRelationNegativeCallTest() { | ||
100 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
101 | not(friend.call(p1, v1)), | ||
102 | friend.call(v1, p1) | ||
103 | ))).getDnf(); | ||
104 | var actual = sut.lift(Modality.MUST, Concreteness.PARTIAL, input); | ||
105 | |||
106 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
107 | not(ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, v1)), | ||
108 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(v1, p1), | ||
109 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1) | ||
110 | ))).getDnf(); | ||
111 | |||
112 | assertThat(actual, structurallyEqualTo(expected)); | ||
113 | } | ||
114 | |||
115 | @Test | ||
116 | void liftPartialRelationQuantifiedNegativeCallTest() { | ||
117 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
118 | person.call(p1), | ||
119 | not(friend.call(p1, v1)) | ||
120 | ))).getDnf(); | ||
121 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
122 | |||
123 | var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause( | ||
124 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, p2), | ||
125 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
126 | )); | ||
127 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
128 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1), | ||
129 | not(helper.call(p1, v1)) | ||
130 | ))).getDnf(); | ||
131 | |||
132 | assertThat(actual, structurallyEqualTo(expected)); | ||
133 | } | ||
134 | |||
135 | @Test | ||
136 | void liftSymbolViewQuantifiedNegativeCallTest() { | ||
137 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
138 | person.call(p1), | ||
139 | not(friendMustView.call(p1, v1)) | ||
140 | ))).getDnf(); | ||
141 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
142 | |||
143 | var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause( | ||
144 | friendMustView.call(p1, p2), | ||
145 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
146 | )); | ||
147 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
148 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1), | ||
149 | not(helper.call(p1, v1)) | ||
150 | ))).getDnf(); | ||
151 | |||
152 | assertThat(actual, structurallyEqualTo(expected)); | ||
153 | } | ||
154 | |||
155 | @Test | ||
156 | void liftPartialRelationQuantifiedNegativeDiagonalCallTest() { | ||
157 | var input = Query.of("Actual", (builder) -> builder.clause((v1) -> List.of( | ||
158 | not(friend.call(v1, v1)) | ||
159 | ))).getDnf(); | ||
160 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
161 | |||
162 | var helper = Query.of("Helper", (builder, p1) -> builder.clause( | ||
163 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, friend).call(p1, p1), | ||
164 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p1) | ||
165 | )); | ||
166 | var expected = Query.of("Expected", (builder) -> builder.clause((v1) -> List.of( | ||
167 | not(helper.call(v1)) | ||
168 | ))).getDnf(); | ||
169 | |||
170 | assertThat(actual, structurallyEqualTo(expected)); | ||
171 | } | ||
172 | |||
173 | @Test | ||
174 | void liftPartialDnfQuantifiedNegativeInputCallTest() { | ||
175 | var called = Dnf.of("Called", builder -> { | ||
176 | var p1 = builder.parameter("p1", ParameterDirection.IN); | ||
177 | var p2 = builder.parameter("p2", ParameterDirection.OUT); | ||
178 | builder.clause( | ||
179 | friend.call(p1, p2), | ||
180 | friend.call(p2, p1) | ||
181 | ); | ||
182 | }); | ||
183 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
184 | person.call(p1), | ||
185 | not(called.call(p1, v1)) | ||
186 | ))).getDnf(); | ||
187 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
188 | |||
189 | var helper = Dnf.of("Helper", builder -> { | ||
190 | var p1 = builder.parameter("p1", ParameterDirection.IN); | ||
191 | var p2 = builder.parameter("p2", ParameterDirection.OUT); | ||
192 | builder.clause( | ||
193 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, called).call(p1, p2), | ||
194 | ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
195 | ); | ||
196 | }); | ||
197 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
198 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1), | ||
199 | not(helper.call(p1, v1)) | ||
200 | ))).getDnf(); | ||
201 | |||
202 | assertThat(actual, structurallyEqualTo(expected)); | ||
203 | } | ||
204 | |||
205 | @Test | ||
206 | void liftPartialRelationTransitiveCallTest() { | ||
207 | var input = Query.of("Actual", (builder, p1, p2)-> builder.clause( | ||
208 | friend.callTransitive(p1, p2), | ||
209 | not(person.call(p2)) | ||
210 | )).getDnf(); | ||
211 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
212 | |||
213 | var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause( | ||
214 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2), | ||
215 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
216 | )); | ||
217 | var helper2 = Query.of("Helper2", (builder, p1, p2) -> { | ||
218 | builder.clause( | ||
219 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2) | ||
220 | ); | ||
221 | builder.clause((v1) -> List.of( | ||
222 | helper.callTransitive(p1, v1), | ||
223 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(v1, p2) | ||
224 | )); | ||
225 | }); | ||
226 | var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause( | ||
227 | helper2.call(p1, p2), | ||
228 | not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(p2)) | ||
229 | )).getDnf(); | ||
230 | |||
231 | assertThat(actual, structurallyEqualTo(expected)); | ||
232 | } | ||
233 | |||
234 | @Test | ||
235 | void liftPartialSymbolTransitiveCallTest() { | ||
236 | var input = Query.of("Actual", (builder, p1, p2)-> builder.clause( | ||
237 | friendMustView.callTransitive(p1, p2), | ||
238 | not(person.call(p2)) | ||
239 | )).getDnf(); | ||
240 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
241 | |||
242 | var endExistsHelper = Query.of("EndExistsHelper", (builder, p1, p2) -> builder.clause( | ||
243 | friendMustView.call(p1, p2), | ||
244 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
245 | )); | ||
246 | var transitiveHelper = Query.of("TransitiveHelper", (builder, p1, p2) -> { | ||
247 | builder.clause( | ||
248 | friendMustView.call(p1, p2) | ||
249 | ); | ||
250 | builder.clause((v1) -> List.of( | ||
251 | endExistsHelper.callTransitive(p1, v1), | ||
252 | friendMustView.call(v1, p2) | ||
253 | )); | ||
254 | }); | ||
255 | var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause( | ||
256 | transitiveHelper.call(p1, p2), | ||
257 | not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(p2)) | ||
258 | )).getDnf(); | ||
259 | |||
260 | assertThat(actual, structurallyEqualTo(expected)); | ||
261 | } | ||
262 | |||
263 | @Test | ||
264 | void liftPartialRelationTransitiveCallExistsTest() { | ||
265 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
266 | friend.callTransitive(p1, v1), | ||
267 | not(person.call(v1)) | ||
268 | ))).getDnf(); | ||
269 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
270 | |||
271 | var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause( | ||
272 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2), | ||
273 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
274 | )); | ||
275 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
276 | helper.callTransitive(p1, v1), | ||
277 | not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(v1)) | ||
278 | ))).getDnf(); | ||
279 | |||
280 | assertThat(actual, structurallyEqualTo(expected)); | ||
281 | } | ||
282 | |||
283 | @Test | ||
284 | void liftMultipleTransitiveCallExistsTest() { | ||
285 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
286 | friend.callTransitive(p1, v1), | ||
287 | friendMustView.callTransitive(p1, v1), | ||
288 | not(person.call(v1)) | ||
289 | ))).getDnf(); | ||
290 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
291 | |||
292 | var helper = Query.of("Helper", (builder, p1, p2) -> builder.clause( | ||
293 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2), | ||
294 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
295 | )); | ||
296 | var helper2 = Query.of("Helper2", (builder, p1, p2) -> builder.clause( | ||
297 | friendMustView.call(p1, p2), | ||
298 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(p2) | ||
299 | )); | ||
300 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
301 | helper.callTransitive(p1, v1), | ||
302 | helper2.callTransitive(p1, v1), | ||
303 | not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, person).call(v1)) | ||
304 | ))).getDnf(); | ||
305 | |||
306 | assertThat(actual, structurallyEqualTo(expected)); | ||
307 | } | ||
308 | |||
309 | @Test | ||
310 | void liftEquivalentTest() { | ||
311 | var input = Query.of("Actual", (builder, p1, p2) -> builder.clause( | ||
312 | p1.isEquivalent(p2), | ||
313 | person.call(p1) | ||
314 | )).getDnf(); | ||
315 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
316 | |||
317 | var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause( | ||
318 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1), | ||
319 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EQUALS_SYMBOL).call(p2, p1) | ||
320 | )).getDnf(); | ||
321 | |||
322 | assertThat(actual, structurallyEqualTo(expected)); | ||
323 | } | ||
324 | |||
325 | @Test | ||
326 | void liftNotEquivalentTest() { | ||
327 | var input = Query.of("Actual", (builder, p1, p2) -> builder.clause( | ||
328 | not(p1.isEquivalent(p2)), | ||
329 | friend.call(p1, p2) | ||
330 | )).getDnf(); | ||
331 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
332 | |||
333 | var expected = Query.of("Expected", (builder, p1, p2) -> builder.clause( | ||
334 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(p1, p2), | ||
335 | not(ModalConstraint.of(Modality.MUST, Concreteness.PARTIAL, ReasoningAdapter.EQUALS_SYMBOL).call(p1, p2)) | ||
336 | )).getDnf(); | ||
337 | |||
338 | assertThat(actual, structurallyEqualTo(expected)); | ||
339 | } | ||
340 | |||
341 | @Test | ||
342 | void liftConstantTest() { | ||
343 | var input = Query.of("Actual", (builder, p1) -> builder.clause((v1) -> List.of( | ||
344 | v1.isConstant(0), | ||
345 | friend.call(v1, p1) | ||
346 | ))).getDnf(); | ||
347 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
348 | |||
349 | var expected = Query.of("Expected", (builder, p1) -> builder.clause((v1) -> List.of( | ||
350 | v1.isConstant(0), | ||
351 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, friend).call(v1, p1), | ||
352 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, ReasoningAdapter.EXISTS_SYMBOL).call(v1) | ||
353 | ))).getDnf(); | ||
354 | |||
355 | assertThat(actual, structurallyEqualTo(expected)); | ||
356 | } | ||
357 | |||
358 | @Test | ||
359 | void liftAssignTest() { | ||
360 | var input = Query.of("Actual", Integer.class, (builder, p1, output) -> builder | ||
361 | .clause(Integer.class, (d1) -> List.of( | ||
362 | person.call(p1), | ||
363 | ageView.call(p1, d1), | ||
364 | output.assign(mul(constant(2), d1)) | ||
365 | ))).getDnf(); | ||
366 | var actual = sut.lift(Modality.MAY, Concreteness.PARTIAL, input); | ||
367 | |||
368 | var expected = Query.of("Expected", Integer.class, (builder, p1, output) -> builder | ||
369 | .clause(Integer.class, (d1) -> List.of( | ||
370 | ModalConstraint.of(Modality.MAY, Concreteness.PARTIAL, person).call(p1), | ||
371 | ageView.call(p1, d1), | ||
372 | output.assign(mul(constant(2), d1)) | ||
373 | ))).getDnf(); | ||
374 | |||
375 | assertThat(actual, structurallyEqualTo(expected)); | ||
376 | } | ||
377 | |||
378 | @Test | ||
379 | void liftCheckTest() { | ||
380 | var input = Query.of("Actual", (builder, p1) -> builder.clause(Integer.class, (d1) -> List.of( | ||
381 | person.call(p1), | ||
382 | ageView.call(p1, d1), | ||
383 | check(greaterEq(d1, constant(21))) | ||
384 | ))).getDnf(); | ||
385 | var actual = sut.lift(Modality.MAY, Concreteness.CANDIDATE, input); | ||
386 | |||
387 | var expected = Query.of("Expected", (builder, p1) -> builder.clause(Integer.class, (d1) -> List.of( | ||
388 | ModalConstraint.of(Modality.MAY, Concreteness.CANDIDATE, person).call(p1), | ||
389 | ageView.call(p1, d1), | ||
390 | check(greaterEq(d1, constant(21))) | ||
391 | ))).getDnf(); | ||
392 | |||
393 | assertThat(actual, structurallyEqualTo(expected)); | ||
394 | } | ||
395 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java new file mode 100644 index 00000000..bbfaff84 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java | |||
@@ -0,0 +1,128 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.containment; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
16 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
17 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
18 | import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity; | ||
19 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy; | ||
20 | import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator; | ||
21 | import tools.refinery.store.representation.TruthValue; | ||
22 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
23 | import tools.refinery.store.tuple.Tuple; | ||
24 | |||
25 | import java.util.Map; | ||
26 | |||
27 | import static org.hamcrest.MatcherAssert.assertThat; | ||
28 | import static org.hamcrest.Matchers.is; | ||
29 | import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINED_SYMBOL; | ||
30 | import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINS_SYMBOL; | ||
31 | import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.COUNT_SYMBOL; | ||
32 | |||
33 | class ContainmentHierarchyTranslatorTest { | ||
34 | private final PartialRelation c1 = new PartialRelation("C1", 1); | ||
35 | private final PartialRelation c2 = new PartialRelation("C2", 1); | ||
36 | private final PartialRelation entry = new PartialRelation("entry", 2); | ||
37 | |||
38 | private ModelStore store; | ||
39 | |||
40 | @BeforeEach | ||
41 | void beforeEach() { | ||
42 | |||
43 | var typeHierarchy = TypeHierarchy.builder() | ||
44 | .type(CONTAINED_SYMBOL, true) | ||
45 | .type(c1) | ||
46 | .type(c2, c1, CONTAINED_SYMBOL) | ||
47 | .build(); | ||
48 | |||
49 | var containmentHierarchy = Map.of( | ||
50 | entry, | ||
51 | new ContainmentInfo(c1, UnconstrainedMultiplicity.INSTANCE, c2) | ||
52 | ); | ||
53 | |||
54 | store = ModelStore.builder() | ||
55 | .with(ViatraModelQueryAdapter.builder()) | ||
56 | .with(ReasoningAdapter.builder()) | ||
57 | .with(new MultiObjectTranslator()) | ||
58 | .with(new TypeHierarchyTranslator(typeHierarchy)) | ||
59 | .with(new ContainmentHierarchyTranslator(containmentHierarchy)) | ||
60 | .build(); | ||
61 | } | ||
62 | |||
63 | @Test | ||
64 | void treeTest() { | ||
65 | var modelSeed = ModelSeed.builder(3) | ||
66 | .seed(COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE)) | ||
67 | .seed(CONTAINED_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
68 | .seed(CONTAINS_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
69 | .seed(c1, builder -> builder | ||
70 | .reducedValue(TruthValue.UNKNOWN) | ||
71 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
72 | .seed(c2, builder -> builder | ||
73 | .put(Tuple.of(1), TruthValue.TRUE) | ||
74 | .put(Tuple.of(2), TruthValue.TRUE)) | ||
75 | .seed(entry, builder -> builder | ||
76 | .reducedValue(TruthValue.UNKNOWN) | ||
77 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
78 | .put(Tuple.of(0, 2), TruthValue.TRUE)) | ||
79 | .build(); | ||
80 | |||
81 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); | ||
82 | var interpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(Concreteness.PARTIAL, | ||
83 | entry); | ||
84 | |||
85 | assertThat(interpretation.get(Tuple.of(0, 0)), is(TruthValue.FALSE)); | ||
86 | assertThat(interpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE)); | ||
87 | assertThat(interpretation.get(Tuple.of(0, 2)), is(TruthValue.TRUE)); | ||
88 | assertThat(interpretation.get(Tuple.of(1, 0)), is(TruthValue.FALSE)); | ||
89 | assertThat(interpretation.get(Tuple.of(1, 1)), is(TruthValue.FALSE)); | ||
90 | assertThat(interpretation.get(Tuple.of(1, 2)), is(TruthValue.FALSE)); | ||
91 | assertThat(interpretation.get(Tuple.of(2, 0)), is(TruthValue.FALSE)); | ||
92 | assertThat(interpretation.get(Tuple.of(2, 1)), is(TruthValue.FALSE)); | ||
93 | assertThat(interpretation.get(Tuple.of(2, 2)), is(TruthValue.FALSE)); | ||
94 | } | ||
95 | |||
96 | @Test | ||
97 | void loopTest() { | ||
98 | var modelSeed = ModelSeed.builder(3) | ||
99 | .seed(COUNT_SYMBOL, builder -> builder.reducedValue(CardinalityIntervals.ONE)) | ||
100 | .seed(CONTAINED_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
101 | .seed(CONTAINS_SYMBOL, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
102 | .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
103 | .seed(c2, builder -> builder | ||
104 | .put(Tuple.of(0), TruthValue.TRUE) | ||
105 | .put(Tuple.of(1), TruthValue.TRUE) | ||
106 | .put(Tuple.of(2), TruthValue.TRUE)) | ||
107 | .seed(entry, builder -> builder | ||
108 | .reducedValue(TruthValue.UNKNOWN) | ||
109 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
110 | .put(Tuple.of(1, 2), TruthValue.TRUE) | ||
111 | .put(Tuple.of(2, 0), TruthValue.TRUE)) | ||
112 | .build(); | ||
113 | |||
114 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); | ||
115 | var interpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(Concreteness.PARTIAL, | ||
116 | entry); | ||
117 | |||
118 | assertThat(interpretation.get(Tuple.of(0, 0)), is(TruthValue.FALSE)); | ||
119 | assertThat(interpretation.get(Tuple.of(0, 1)), is(TruthValue.ERROR)); | ||
120 | assertThat(interpretation.get(Tuple.of(0, 2)), is(TruthValue.FALSE)); | ||
121 | assertThat(interpretation.get(Tuple.of(1, 0)), is(TruthValue.FALSE)); | ||
122 | assertThat(interpretation.get(Tuple.of(1, 1)), is(TruthValue.FALSE)); | ||
123 | assertThat(interpretation.get(Tuple.of(1, 2)), is(TruthValue.ERROR)); | ||
124 | assertThat(interpretation.get(Tuple.of(2, 0)), is(TruthValue.ERROR)); | ||
125 | assertThat(interpretation.get(Tuple.of(2, 1)), is(TruthValue.FALSE)); | ||
126 | assertThat(interpretation.get(Tuple.of(2, 2)), is(TruthValue.FALSE)); | ||
127 | } | ||
128 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java new file mode 100644 index 00000000..0f1a1006 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java | |||
@@ -0,0 +1,58 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
11 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
12 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
13 | |||
14 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
15 | |||
16 | class MetamodelBuilderTest { | ||
17 | private final PartialRelation university = new PartialRelation("University", 1); | ||
18 | private final PartialRelation course = new PartialRelation("Course", 1); | ||
19 | private final PartialRelation courses = new PartialRelation("courses", 2); | ||
20 | private final PartialRelation location = new PartialRelation("location", 2); | ||
21 | |||
22 | @Test | ||
23 | void missingOppositeTest() { | ||
24 | var builder = Metamodel.builder() | ||
25 | .type(university) | ||
26 | .type(course) | ||
27 | .reference(courses, university, course, location) | ||
28 | .reference(location, course, university); | ||
29 | |||
30 | assertThrows(TranslationException.class, builder::build); | ||
31 | } | ||
32 | |||
33 | @Test | ||
34 | void invalidOppositeTypeTest() { | ||
35 | var builder = Metamodel.builder() | ||
36 | .type(university) | ||
37 | .type(course) | ||
38 | .reference(courses, university, course, location) | ||
39 | .reference(location, course, course, courses); | ||
40 | |||
41 | assertThrows(TranslationException.class, builder::build); | ||
42 | } | ||
43 | |||
44 | @Test | ||
45 | void invalidOppositeMultiplicityTest() { | ||
46 | var invalidMultiplicity = new PartialRelation("invalidMultiplicity", 1); | ||
47 | |||
48 | var builder = Metamodel.builder() | ||
49 | .type(university) | ||
50 | .type(course) | ||
51 | .reference(courses, university, true, course, location) | ||
52 | .reference(location, course, | ||
53 | ConstrainedMultiplicity.of(CardinalityIntervals.atLeast(2), invalidMultiplicity), | ||
54 | university, courses); | ||
55 | |||
56 | assertThrows(TranslationException.class, builder::build); | ||
57 | } | ||
58 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java new file mode 100644 index 00000000..eabbdffe --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java | |||
@@ -0,0 +1,152 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.metamodel; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.Model; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
16 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
17 | import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator; | ||
18 | import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator; | ||
19 | import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity; | ||
20 | import tools.refinery.store.representation.TruthValue; | ||
21 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
22 | import tools.refinery.store.tuple.Tuple; | ||
23 | |||
24 | import static org.hamcrest.MatcherAssert.assertThat; | ||
25 | import static org.hamcrest.Matchers.is; | ||
26 | |||
27 | class MetamodelTest { | ||
28 | private final PartialRelation person = new PartialRelation("Person", 1); | ||
29 | private final PartialRelation student = new PartialRelation("Student", 1); | ||
30 | private final PartialRelation teacher = new PartialRelation("Teacher", 1); | ||
31 | private final PartialRelation university = new PartialRelation("University", 1); | ||
32 | private final PartialRelation course = new PartialRelation("Course", 1); | ||
33 | private final PartialRelation courses = new PartialRelation("courses", 2); | ||
34 | private final PartialRelation location = new PartialRelation("location", 2); | ||
35 | private final PartialRelation lecturer = new PartialRelation("lecturer", 2); | ||
36 | private final PartialRelation invalidLecturerCount = new PartialRelation("invalidLecturerCount", 1); | ||
37 | private final PartialRelation enrolledStudents = new PartialRelation("enrolledStudents", 2); | ||
38 | private final PartialRelation invalidStudentCount = new PartialRelation("invalidStudentCount", 1); | ||
39 | |||
40 | @Test | ||
41 | void metamodelTest() { | ||
42 | var metamodel = Metamodel.builder() | ||
43 | .type(person, true) | ||
44 | .type(student, person) | ||
45 | .type(teacher, person) | ||
46 | .type(university) | ||
47 | .type(course) | ||
48 | .reference(courses, university, true, course, location) | ||
49 | .reference(location, course, university, courses) | ||
50 | .reference(lecturer, course, | ||
51 | ConstrainedMultiplicity.of(CardinalityIntervals.ONE, invalidLecturerCount), teacher) | ||
52 | .reference(enrolledStudents, course, | ||
53 | ConstrainedMultiplicity.of(CardinalityIntervals.SOME, invalidStudentCount), student) | ||
54 | .build(); | ||
55 | |||
56 | var seed = ModelSeed.builder(5) | ||
57 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
58 | .reducedValue(CardinalityIntervals.ONE) | ||
59 | .put(Tuple.of(1), CardinalityIntervals.SET) | ||
60 | .put(Tuple.of(4), CardinalityIntervals.SET)) | ||
61 | .seed(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, builder -> builder | ||
62 | .reducedValue(TruthValue.UNKNOWN)) | ||
63 | .seed(ContainmentHierarchyTranslator.CONTAINS_SYMBOL, builder -> builder | ||
64 | .reducedValue(TruthValue.UNKNOWN)) | ||
65 | .seed(person, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
66 | .seed(student, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
67 | .seed(teacher, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
68 | .seed(university, builder -> builder | ||
69 | .reducedValue(TruthValue.UNKNOWN) | ||
70 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
71 | .seed(course, builder -> builder | ||
72 | .reducedValue(TruthValue.UNKNOWN) | ||
73 | .put(Tuple.of(2), TruthValue.TRUE)) | ||
74 | .seed(courses, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
75 | .seed(location, builder -> builder | ||
76 | .reducedValue(TruthValue.UNKNOWN) | ||
77 | .put(Tuple.of(1, 0), TruthValue.TRUE)) | ||
78 | .seed(lecturer, builder -> builder | ||
79 | .reducedValue(TruthValue.FALSE) | ||
80 | .put(Tuple.of(1, 3), TruthValue.TRUE)) | ||
81 | .seed(enrolledStudents, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
82 | .build(); | ||
83 | |||
84 | var model = createModel(metamodel, seed); | ||
85 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
86 | |||
87 | var coursesInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, courses); | ||
88 | assertThat(coursesInterpretation.get(Tuple.of(0, 1)), is(TruthValue.TRUE)); | ||
89 | assertThat(coursesInterpretation.get(Tuple.of(0, 2)), is(TruthValue.UNKNOWN)); | ||
90 | assertThat(coursesInterpretation.get(Tuple.of(0, 3)), is(TruthValue.FALSE)); | ||
91 | |||
92 | var invalidLecturerCountInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, | ||
93 | invalidLecturerCount); | ||
94 | assertThat(invalidLecturerCountInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
95 | assertThat(invalidLecturerCountInterpretation.get(Tuple.of(2)), is(TruthValue.ERROR)); | ||
96 | |||
97 | var enrolledStudentsInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, | ||
98 | enrolledStudents); | ||
99 | assertThat(enrolledStudentsInterpretation.get(Tuple.of(1, 3)), is(TruthValue.FALSE)); | ||
100 | assertThat(enrolledStudentsInterpretation.get(Tuple.of(1, 4)), is(TruthValue.UNKNOWN)); | ||
101 | } | ||
102 | |||
103 | @Test | ||
104 | void simpleContainmentTest() { | ||
105 | var metamodel = Metamodel.builder() | ||
106 | .type(university) | ||
107 | .type(course) | ||
108 | .reference(courses, university, true, course) | ||
109 | .build(); | ||
110 | |||
111 | |||
112 | var seed = ModelSeed.builder(4) | ||
113 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
114 | .reducedValue(CardinalityIntervals.ONE) | ||
115 | .put(Tuple.of(0), CardinalityIntervals.SET) | ||
116 | .put(Tuple.of(1), CardinalityIntervals.SET)) | ||
117 | .seed(ContainmentHierarchyTranslator.CONTAINED_SYMBOL, builder -> builder | ||
118 | .reducedValue(TruthValue.UNKNOWN)) | ||
119 | .seed(ContainmentHierarchyTranslator.CONTAINS_SYMBOL, builder -> builder | ||
120 | .reducedValue(TruthValue.UNKNOWN)) | ||
121 | .seed(university, builder -> builder | ||
122 | .reducedValue(TruthValue.UNKNOWN) | ||
123 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
124 | .seed(course, builder -> builder | ||
125 | .reducedValue(TruthValue.UNKNOWN) | ||
126 | .put(Tuple.of(1), TruthValue.TRUE)) | ||
127 | .seed(courses, builder -> builder | ||
128 | .reducedValue(TruthValue.UNKNOWN) | ||
129 | .put(Tuple.of(2, 3), TruthValue.TRUE)) | ||
130 | .build(); | ||
131 | |||
132 | var model = createModel(metamodel, seed); | ||
133 | var coursesInterpretation = model.getAdapter(ReasoningAdapter.class) | ||
134 | .getPartialInterpretation(Concreteness.PARTIAL, courses); | ||
135 | |||
136 | assertThat(coursesInterpretation.get(Tuple.of(0, 1)), is(TruthValue.UNKNOWN)); | ||
137 | assertThat(coursesInterpretation.get(Tuple.of(0, 3)), is(TruthValue.FALSE)); | ||
138 | assertThat(coursesInterpretation.get(Tuple.of(2, 1)), is(TruthValue.UNKNOWN)); | ||
139 | assertThat(coursesInterpretation.get(Tuple.of(2, 3)), is(TruthValue.TRUE)); | ||
140 | } | ||
141 | |||
142 | private static Model createModel(Metamodel metamodel, ModelSeed seed) { | ||
143 | var store = ModelStore.builder() | ||
144 | .with(ViatraModelQueryAdapter.builder()) | ||
145 | .with(ReasoningAdapter.builder()) | ||
146 | .with(new MultiObjectTranslator()) | ||
147 | .with(new MetamodelTranslator(metamodel)) | ||
148 | .build(); | ||
149 | |||
150 | return store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
151 | } | ||
152 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java new file mode 100644 index 00000000..64230cf6 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java | |||
@@ -0,0 +1,321 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.multiobject; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.model.ModelStore; | ||
10 | import tools.refinery.store.query.ModelQueryAdapter; | ||
11 | import tools.refinery.store.query.dnf.Query; | ||
12 | import tools.refinery.store.query.resultset.ResultSet; | ||
13 | import tools.refinery.store.query.term.Variable; | ||
14 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
15 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
16 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
17 | import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral; | ||
18 | import tools.refinery.store.reasoning.literal.CountUpperBoundLiteral; | ||
19 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
20 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
21 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | ||
22 | import tools.refinery.store.representation.Symbol; | ||
23 | import tools.refinery.store.representation.TruthValue; | ||
24 | import tools.refinery.store.representation.cardinality.CardinalityIntervals; | ||
25 | import tools.refinery.store.representation.cardinality.UpperCardinalities; | ||
26 | import tools.refinery.store.representation.cardinality.UpperCardinality; | ||
27 | import tools.refinery.store.tuple.Tuple; | ||
28 | |||
29 | import java.util.List; | ||
30 | |||
31 | import static org.hamcrest.MatcherAssert.assertThat; | ||
32 | import static org.hamcrest.Matchers.is; | ||
33 | import static tools.refinery.store.query.literal.Literals.not; | ||
34 | import static tools.refinery.store.reasoning.literal.PartialLiterals.must; | ||
35 | |||
36 | class PartialCountTest { | ||
37 | private static final PartialRelation person = new PartialRelation("Person", 1); | ||
38 | private static final PartialRelation friend = new PartialRelation("friend", 2); | ||
39 | |||
40 | @Test | ||
41 | void lowerBoundZeroTest() { | ||
42 | var query = Query.of("LowerBound", Integer.class, (builder, p1, p2, output) -> builder.clause( | ||
43 | must(person.call(p1)), | ||
44 | must(person.call(p2)), | ||
45 | new CountLowerBoundLiteral(output, friend, List.of(p1, p2)) | ||
46 | )); | ||
47 | |||
48 | var modelSeed = ModelSeed.builder(2) | ||
49 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
50 | .put(Tuple.of(0), CardinalityIntervals.atLeast(3)) | ||
51 | .put(Tuple.of(1), CardinalityIntervals.atMost(7))) | ||
52 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
53 | .seed(friend, builder -> builder | ||
54 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
55 | .put(Tuple.of(1, 0), TruthValue.UNKNOWN) | ||
56 | .put(Tuple.of(1, 1), TruthValue.ERROR)) | ||
57 | .build(); | ||
58 | |||
59 | var resultSet = getResultSet(query, modelSeed); | ||
60 | assertThat(resultSet.get(Tuple.of(0, 0)), is(0)); | ||
61 | assertThat(resultSet.get(Tuple.of(0, 1)), is(1)); | ||
62 | assertThat(resultSet.get(Tuple.of(1, 0)), is(0)); | ||
63 | assertThat(resultSet.get(Tuple.of(1, 1)), is(1)); | ||
64 | } | ||
65 | |||
66 | @Test | ||
67 | void upperBoundZeroTest() { | ||
68 | var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, p2, output) -> builder.clause( | ||
69 | must(person.call(p1)), | ||
70 | must(person.call(p2)), | ||
71 | new CountUpperBoundLiteral(output, friend, List.of(p1, p2)) | ||
72 | )); | ||
73 | |||
74 | var modelSeed = ModelSeed.builder(2) | ||
75 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
76 | .put(Tuple.of(0), CardinalityIntervals.atLeast(3)) | ||
77 | .put(Tuple.of(1), CardinalityIntervals.atMost(7))) | ||
78 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
79 | .seed(friend, builder -> builder | ||
80 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
81 | .put(Tuple.of(1, 0), TruthValue.UNKNOWN) | ||
82 | .put(Tuple.of(1, 1), TruthValue.ERROR)) | ||
83 | .build(); | ||
84 | |||
85 | var resultSet = getResultSet(query, modelSeed); | ||
86 | assertThat(resultSet.get(Tuple.of(0, 0)), is(UpperCardinalities.ZERO)); | ||
87 | assertThat(resultSet.get(Tuple.of(0, 1)), is(UpperCardinalities.ONE)); | ||
88 | assertThat(resultSet.get(Tuple.of(1, 0)), is(UpperCardinalities.ONE)); | ||
89 | assertThat(resultSet.get(Tuple.of(1, 1)), is(UpperCardinalities.ZERO)); | ||
90 | } | ||
91 | |||
92 | @Test | ||
93 | void lowerBoundOneTest() { | ||
94 | var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause( | ||
95 | must(person.call(p1)), | ||
96 | new CountLowerBoundLiteral(output, friend, List.of(p1, Variable.of())) | ||
97 | )); | ||
98 | |||
99 | var modelSeed = ModelSeed.builder(4) | ||
100 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
101 | .reducedValue(CardinalityIntervals.ONE) | ||
102 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
103 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
104 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
105 | .seed(friend, builder -> builder | ||
106 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
107 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
108 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
109 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
110 | .put(Tuple.of(1, 2), TruthValue.UNKNOWN) | ||
111 | .put(Tuple.of(1, 3), TruthValue.UNKNOWN) | ||
112 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
113 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
114 | .build(); | ||
115 | |||
116 | var resultSet = getResultSet(query, modelSeed); | ||
117 | assertThat(resultSet.get(Tuple.of(0)), is(4)); | ||
118 | assertThat(resultSet.get(Tuple.of(1)), is(1)); | ||
119 | assertThat(resultSet.get(Tuple.of(2)), is(4)); | ||
120 | assertThat(resultSet.get(Tuple.of(3)), is(0)); | ||
121 | } | ||
122 | |||
123 | @Test | ||
124 | void upperBoundOneTest() { | ||
125 | var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder.clause( | ||
126 | must(person.call(p1)), | ||
127 | new CountUpperBoundLiteral(output, friend, List.of(p1, Variable.of())) | ||
128 | )); | ||
129 | |||
130 | var modelSeed = ModelSeed.builder(4) | ||
131 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
132 | .reducedValue(CardinalityIntervals.ONE) | ||
133 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
134 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
135 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
136 | .seed(friend, builder -> builder | ||
137 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
138 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
139 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
140 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
141 | .put(Tuple.of(1, 2), TruthValue.UNKNOWN) | ||
142 | .put(Tuple.of(1, 3), TruthValue.UNKNOWN) | ||
143 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
144 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
145 | .build(); | ||
146 | |||
147 | var resultSet = getResultSet(query, modelSeed); | ||
148 | assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED)); | ||
149 | assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(9))); | ||
150 | assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.ONE)); | ||
151 | assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO)); | ||
152 | } | ||
153 | |||
154 | @Test | ||
155 | void lowerBoundTwoTest() { | ||
156 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause( | ||
157 | friend.call(p1, p2), | ||
158 | friend.call(p1, p3), | ||
159 | friend.call(p2, p3) | ||
160 | )); | ||
161 | var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause( | ||
162 | must(person.call(p1)), | ||
163 | new CountLowerBoundLiteral(output, subQuery.getDnf(), List.of(p1, Variable.of(), Variable.of())) | ||
164 | )); | ||
165 | |||
166 | var modelSeed = ModelSeed.builder(4) | ||
167 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
168 | .reducedValue(CardinalityIntervals.ONE) | ||
169 | .put(Tuple.of(0), CardinalityIntervals.between(5, 9)) | ||
170 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
171 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
172 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
173 | .seed(friend, builder -> builder | ||
174 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
175 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
176 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
177 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
178 | .put(Tuple.of(1, 2), TruthValue.TRUE) | ||
179 | .put(Tuple.of(1, 3), TruthValue.TRUE) | ||
180 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
181 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
182 | .build(); | ||
183 | |||
184 | var resultSet = getResultSet(query, modelSeed); | ||
185 | assertThat(resultSet.get(Tuple.of(0)), is(3)); | ||
186 | assertThat(resultSet.get(Tuple.of(1)), is(5)); | ||
187 | assertThat(resultSet.get(Tuple.of(2)), is(30)); | ||
188 | assertThat(resultSet.get(Tuple.of(3)), is(0)); | ||
189 | } | ||
190 | |||
191 | @Test | ||
192 | void upperBoundTwoTest() { | ||
193 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause( | ||
194 | friend.call(p1, p2), | ||
195 | friend.call(p1, p3), | ||
196 | friend.call(p2, p3) | ||
197 | )); | ||
198 | var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder.clause( | ||
199 | must(person.call(p1)), | ||
200 | new CountUpperBoundLiteral(output, subQuery.getDnf(), List.of(p1, Variable.of(), Variable.of())) | ||
201 | )); | ||
202 | |||
203 | var modelSeed = ModelSeed.builder(4) | ||
204 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
205 | .reducedValue(CardinalityIntervals.ONE) | ||
206 | .put(Tuple.of(0), CardinalityIntervals.between(5, 9)) | ||
207 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
208 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
209 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
210 | .seed(friend, builder -> builder | ||
211 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
212 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
213 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
214 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
215 | .put(Tuple.of(1, 2), TruthValue.UNKNOWN) | ||
216 | .put(Tuple.of(1, 3), TruthValue.UNKNOWN) | ||
217 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
218 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
219 | .build(); | ||
220 | |||
221 | var resultSet = getResultSet(query, modelSeed); | ||
222 | assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED)); | ||
223 | assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(135))); | ||
224 | assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.ZERO)); | ||
225 | assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO)); | ||
226 | } | ||
227 | |||
228 | @Test | ||
229 | void lowerBoundDiagonalTest() { | ||
230 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause( | ||
231 | friend.call(p1, p2), | ||
232 | friend.call(p1, p3), | ||
233 | not(friend.call(p2, p3)) | ||
234 | )); | ||
235 | var query = Query.of("LowerBound", Integer.class, (builder, p1, output) -> builder.clause(v1 -> List.of( | ||
236 | must(person.call(p1)), | ||
237 | new CountLowerBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1)) | ||
238 | ))); | ||
239 | |||
240 | var modelSeed = ModelSeed.builder(4) | ||
241 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
242 | .reducedValue(CardinalityIntervals.ONE) | ||
243 | .put(Tuple.of(0), CardinalityIntervals.between(5, 9)) | ||
244 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
245 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
246 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
247 | .seed(friend, builder -> builder | ||
248 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
249 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
250 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
251 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
252 | .put(Tuple.of(1, 2), TruthValue.UNKNOWN) | ||
253 | .put(Tuple.of(1, 3), TruthValue.UNKNOWN) | ||
254 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
255 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
256 | .build(); | ||
257 | |||
258 | var resultSet = getResultSet(query, modelSeed); | ||
259 | assertThat(resultSet.get(Tuple.of(0)), is(4)); | ||
260 | assertThat(resultSet.get(Tuple.of(1)), is(5)); | ||
261 | assertThat(resultSet.get(Tuple.of(2)), is(8)); | ||
262 | assertThat(resultSet.get(Tuple.of(3)), is(0)); | ||
263 | } | ||
264 | |||
265 | @Test | ||
266 | void upperBoundDiagonalTest() { | ||
267 | var subQuery = Query.of("SubQuery", (builder, p1, p2, p3) -> builder.clause( | ||
268 | friend.call(p1, p2), | ||
269 | friend.call(p1, p3), | ||
270 | not(friend.call(p2, p3)) | ||
271 | )); | ||
272 | var query = Query.of("UpperBound", UpperCardinality.class, (builder, p1, output) -> builder | ||
273 | .clause(v1 -> List.of( | ||
274 | must(person.call(p1)), | ||
275 | new CountUpperBoundLiteral(output, subQuery.getDnf(), List.of(p1, v1, v1)) | ||
276 | ))); | ||
277 | |||
278 | var modelSeed = ModelSeed.builder(4) | ||
279 | .seed(MultiObjectTranslator.COUNT_SYMBOL, builder -> builder | ||
280 | .reducedValue(CardinalityIntervals.ONE) | ||
281 | .put(Tuple.of(0), CardinalityIntervals.between(5, 9)) | ||
282 | .put(Tuple.of(1), CardinalityIntervals.atLeast(3)) | ||
283 | .put(Tuple.of(2), CardinalityIntervals.atMost(7))) | ||
284 | .seed(person, builder -> builder.reducedValue(TruthValue.TRUE)) | ||
285 | .seed(friend, builder -> builder | ||
286 | .put(Tuple.of(0, 1), TruthValue.TRUE) | ||
287 | .put(Tuple.of(0, 2), TruthValue.TRUE) | ||
288 | .put(Tuple.of(0, 3), TruthValue.TRUE) | ||
289 | .put(Tuple.of(1, 0), TruthValue.TRUE) | ||
290 | .put(Tuple.of(1, 2), TruthValue.UNKNOWN) | ||
291 | .put(Tuple.of(1, 3), TruthValue.UNKNOWN) | ||
292 | .put(Tuple.of(2, 0), TruthValue.TRUE) | ||
293 | .put(Tuple.of(2, 1), TruthValue.ERROR)) | ||
294 | .build(); | ||
295 | |||
296 | var resultSet = getResultSet(query, modelSeed); | ||
297 | assertThat(resultSet.get(Tuple.of(0)), is(UpperCardinalities.UNBOUNDED)); | ||
298 | assertThat(resultSet.get(Tuple.of(1)), is(UpperCardinalities.atMost(17))); | ||
299 | assertThat(resultSet.get(Tuple.of(2)), is(UpperCardinalities.atMost(9))); | ||
300 | assertThat(resultSet.get(Tuple.of(3)), is(UpperCardinalities.ZERO)); | ||
301 | } | ||
302 | |||
303 | private static <T> ResultSet<T> getResultSet(Query<T> query, ModelSeed modelSeed) { | ||
304 | var personStorage = Symbol.of("Person", 1, TruthValue.class, TruthValue.FALSE); | ||
305 | var friendStorage = Symbol.of("friend", 2, TruthValue.class, TruthValue.FALSE); | ||
306 | |||
307 | var store = ModelStore.builder() | ||
308 | .with(ViatraModelQueryAdapter.builder() | ||
309 | .query(query)) | ||
310 | .with(ReasoningAdapter.builder()) | ||
311 | .with(new MultiObjectTranslator()) | ||
312 | .with(PartialRelationTranslator.of(person) | ||
313 | .symbol(personStorage)) | ||
314 | .with(PartialRelationTranslator.of(friend) | ||
315 | .symbol(friendStorage)) | ||
316 | .build(); | ||
317 | |||
318 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed); | ||
319 | return model.getAdapter(ModelQueryAdapter.class).getResultSet(query); | ||
320 | } | ||
321 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java new file mode 100644 index 00000000..3658d603 --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java | |||
@@ -0,0 +1,145 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.model.ModelStore; | ||
11 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
12 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
13 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
14 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
16 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
17 | import tools.refinery.store.representation.TruthValue; | ||
18 | import tools.refinery.store.tuple.Tuple; | ||
19 | |||
20 | import static org.hamcrest.MatcherAssert.assertThat; | ||
21 | import static org.hamcrest.Matchers.is; | ||
22 | |||
23 | class ConcreteSupertypeTest { | ||
24 | private final PartialRelation c1 = new PartialRelation("C1", 1); | ||
25 | private final PartialRelation c2 = new PartialRelation("C2", 1); | ||
26 | |||
27 | private ModelStore store; | ||
28 | |||
29 | @BeforeEach | ||
30 | void beforeEach() { | ||
31 | var typeHierarchy = TypeHierarchy.builder() | ||
32 | .type(c1) | ||
33 | .type(c2, c1) | ||
34 | .build(); | ||
35 | |||
36 | store = ModelStore.builder() | ||
37 | .with(ViatraModelQueryAdapter.builder()) | ||
38 | .with(ReasoningAdapter.builder()) | ||
39 | .with(new TypeHierarchyTranslator(typeHierarchy)) | ||
40 | .build(); | ||
41 | } | ||
42 | |||
43 | @Test | ||
44 | void inheritedTypeTrueTest() { | ||
45 | var seed = ModelSeed.builder(1) | ||
46 | .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
47 | .seed(c2, builder -> builder | ||
48 | .reducedValue(TruthValue.UNKNOWN) | ||
49 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
50 | .build(); | ||
51 | |||
52 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
53 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
54 | |||
55 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
56 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
57 | } | ||
58 | |||
59 | @Test | ||
60 | void inheritedTypeFalseTest() { | ||
61 | var seed = ModelSeed.builder(1) | ||
62 | .seed(c1, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
63 | .seed(c2, builder -> builder | ||
64 | .reducedValue(TruthValue.UNKNOWN) | ||
65 | .put(Tuple.of(0), TruthValue.FALSE)) | ||
66 | .build(); | ||
67 | |||
68 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
69 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
70 | |||
71 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), | ||
72 | is(TruthValue.UNKNOWN)); | ||
73 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
74 | } | ||
75 | |||
76 | @Test | ||
77 | void supertypeTrueTest() { | ||
78 | var seed = ModelSeed.builder(1) | ||
79 | .seed(c1, builder -> builder | ||
80 | .reducedValue(TruthValue.UNKNOWN) | ||
81 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
82 | .seed(c2, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
83 | .build(); | ||
84 | |||
85 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
86 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
87 | |||
88 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
89 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), | ||
90 | is(TruthValue.UNKNOWN)); | ||
91 | } | ||
92 | |||
93 | @Test | ||
94 | void supertypeFalseTest() { | ||
95 | var seed = ModelSeed.builder(1) | ||
96 | .seed(c1, builder -> builder | ||
97 | .reducedValue(TruthValue.UNKNOWN) | ||
98 | .put(Tuple.of(0), TruthValue.FALSE)) | ||
99 | .seed(c2, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
100 | .build(); | ||
101 | |||
102 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
103 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
104 | |||
105 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
106 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
107 | } | ||
108 | |||
109 | @Test | ||
110 | void supertypeOnlyTest() { | ||
111 | var seed = ModelSeed.builder(1) | ||
112 | .seed(c1, builder -> builder | ||
113 | .reducedValue(TruthValue.UNKNOWN) | ||
114 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
115 | .seed(c2, builder -> builder | ||
116 | .reducedValue(TruthValue.UNKNOWN) | ||
117 | .put(Tuple.of(0), TruthValue.FALSE)) | ||
118 | .build(); | ||
119 | |||
120 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
121 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
122 | |||
123 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
124 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
125 | } | ||
126 | |||
127 | |||
128 | @Test | ||
129 | void inheritedTypeErrorTest() { | ||
130 | var seed = ModelSeed.builder(1) | ||
131 | .seed(c1, builder -> builder | ||
132 | .reducedValue(TruthValue.UNKNOWN) | ||
133 | .put(Tuple.of(0), TruthValue.FALSE)) | ||
134 | .seed(c2, builder -> builder | ||
135 | .reducedValue(TruthValue.UNKNOWN) | ||
136 | .put(Tuple.of(0), TruthValue.TRUE)) | ||
137 | .build(); | ||
138 | |||
139 | var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
140 | var adapter = model.getAdapter(ReasoningAdapter.class); | ||
141 | |||
142 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c1).get(Tuple.of(0)), is(TruthValue.ERROR)); | ||
143 | assertThat(adapter.getPartialInterpretation(Concreteness.PARTIAL, c2).get(Tuple.of(0)), is(TruthValue.ERROR)); | ||
144 | } | ||
145 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisExampleHierarchyTest.java index 05a476c6..d9a5477e 100644 --- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisExampleHierarchyTest.java | |||
@@ -5,19 +5,19 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | 6 | package tools.refinery.store.reasoning.translator.typehierarchy; |
7 | 7 | ||
8 | import org.hamcrest.Matchers; | ||
8 | import org.junit.jupiter.api.BeforeEach; | 9 | import org.junit.jupiter.api.BeforeEach; |
9 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | 11 | import tools.refinery.store.reasoning.representation.PartialRelation; |
11 | import tools.refinery.store.representation.TruthValue; | 12 | import tools.refinery.store.representation.TruthValue; |
12 | 13 | ||
13 | import java.util.LinkedHashMap; | ||
14 | import java.util.Set; | 14 | import java.util.Set; |
15 | 15 | ||
16 | import static org.hamcrest.MatcherAssert.assertThat; | 16 | import static org.hamcrest.MatcherAssert.assertThat; |
17 | import static org.hamcrest.Matchers.is; | 17 | import static org.hamcrest.Matchers.is; |
18 | import static org.junit.jupiter.api.Assertions.assertAll; | 18 | import static org.junit.jupiter.api.Assertions.assertAll; |
19 | 19 | ||
20 | class TypeAnalyzerExampleHierarchyTest { | 20 | class TypeAnalysisExampleHierarchyTest { |
21 | private final PartialRelation a1 = new PartialRelation("A1", 1); | 21 | private final PartialRelation a1 = new PartialRelation("A1", 1); |
22 | private final PartialRelation a2 = new PartialRelation("A2", 1); | 22 | private final PartialRelation a2 = new PartialRelation("A2", 1); |
23 | private final PartialRelation a3 = new PartialRelation("A3", 1); | 23 | private final PartialRelation a3 = new PartialRelation("A3", 1); |
@@ -28,23 +28,23 @@ class TypeAnalyzerExampleHierarchyTest { | |||
28 | private final PartialRelation c3 = new PartialRelation("C3", 1); | 28 | private final PartialRelation c3 = new PartialRelation("C3", 1); |
29 | private final PartialRelation c4 = new PartialRelation("C4", 1); | 29 | private final PartialRelation c4 = new PartialRelation("C4", 1); |
30 | 30 | ||
31 | private TypeAnalyzer sut; | 31 | private TypeHierarchy sut; |
32 | private TypeAnalyzerTester tester; | 32 | private TypeHierarchyTester tester; |
33 | 33 | ||
34 | @BeforeEach | 34 | @BeforeEach |
35 | void beforeEach() { | 35 | void beforeEach() { |
36 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | 36 | sut = TypeHierarchy.builder() |
37 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | 37 | .type(a1, true) |
38 | typeInfoMap.put(a2, TypeInfo.builder().abstractType().build()); | 38 | .type(a2, true) |
39 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | 39 | .type(a3, true) |
40 | typeInfoMap.put(a4, TypeInfo.builder().abstractType().build()); | 40 | .type(a4, true) |
41 | typeInfoMap.put(a5, TypeInfo.builder().abstractType().build()); | 41 | .type(a5, true) |
42 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a1, a4).build()); | 42 | .type(c1, a1, a4) |
43 | typeInfoMap.put(c2, TypeInfo.builder().supertypes(a1, a2, a3, a4).build()); | 43 | .type(c2, a1, a2, a3, a4) |
44 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a3).build()); | 44 | .type(c3, a3) |
45 | typeInfoMap.put(c4, TypeInfo.builder().supertype(a4).build()); | 45 | .type(c4, a4) |
46 | sut = new TypeAnalyzer(typeInfoMap); | 46 | .build(); |
47 | tester = new TypeAnalyzerTester(sut); | 47 | tester = new TypeHierarchyTester(sut); |
48 | } | 48 | } |
49 | 49 | ||
50 | @Test | 50 | @Test |
@@ -65,16 +65,16 @@ class TypeAnalyzerExampleHierarchyTest { | |||
65 | @Test | 65 | @Test |
66 | void inferredTypesTest() { | 66 | void inferredTypesTest() { |
67 | assertAll( | 67 | assertAll( |
68 | () -> assertThat(sut.getUnknownType(), is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))), | 68 | () -> assertThat(sut.getUnknownType(), Matchers.is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))), |
69 | () -> assertThat(tester.getInferredType(a1), is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))), | 69 | () -> assertThat(tester.getInferredType(a1), Matchers.is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))), |
70 | () -> assertThat(tester.getInferredType(a3), is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))), | 70 | () -> assertThat(tester.getInferredType(a3), Matchers.is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))), |
71 | () -> assertThat(tester.getInferredType(a4), is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))), | 71 | () -> assertThat(tester.getInferredType(a4), Matchers.is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))), |
72 | () -> assertThat(tester.getInferredType(a5), is(new InferredType(Set.of(a5), Set.of(), null))), | 72 | () -> assertThat(tester.getInferredType(a5), Matchers.is(new InferredType(Set.of(a5), Set.of(), null))), |
73 | () -> assertThat(tester.getInferredType(c1), is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), | 73 | () -> assertThat(tester.getInferredType(c1), Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), |
74 | () -> assertThat(tester.getInferredType(c2), | 74 | () -> assertThat(tester.getInferredType(c2), |
75 | is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))), | 75 | Matchers.is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))), |
76 | () -> assertThat(tester.getInferredType(c3), is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | 76 | () -> assertThat(tester.getInferredType(c3), Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), |
77 | () -> assertThat(tester.getInferredType(c4), is(new InferredType(Set.of(a4, c4), Set.of(c4), c4))) | 77 | () -> assertThat(tester.getInferredType(c4), Matchers.is(new InferredType(Set.of(a4, c4), Set.of(c4), c4))) |
78 | ); | 78 | ); |
79 | } | 79 | } |
80 | 80 | ||
@@ -84,8 +84,8 @@ class TypeAnalyzerExampleHierarchyTest { | |||
84 | var a3Result = tester.getPreservedType(a3); | 84 | var a3Result = tester.getPreservedType(a3); |
85 | var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2); | 85 | var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2); |
86 | assertAll( | 86 | assertAll( |
87 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), is(expected)), | 87 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), Matchers.is(expected)), |
88 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), is(expected)), | 88 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), Matchers.is(expected)), |
89 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())), | 89 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())), |
90 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())), | 90 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())), |
91 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE), | 91 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE), |
@@ -100,19 +100,19 @@ class TypeAnalyzerExampleHierarchyTest { | |||
100 | var a4Result = tester.getPreservedType(a4); | 100 | var a4Result = tester.getPreservedType(a4); |
101 | assertAll( | 101 | assertAll( |
102 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE), | 102 | () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE), |
103 | is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | 103 | Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), |
104 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | 104 | () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE), |
105 | is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), | 105 | Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))), |
106 | () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE), | 106 | () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE), |
107 | is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), | 107 | Matchers.is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))), |
108 | () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE), | 108 | () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE), |
109 | is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))), | 109 | Matchers.is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))), |
110 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE), | 110 | () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE), |
111 | is(new InferredType(Set.of(), Set.of(c3, c4), null))), | 111 | Matchers.is(new InferredType(Set.of(), Set.of(c3, c4), null))), |
112 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE), | 112 | () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE), |
113 | is(new InferredType(Set.of(), Set.of(c1, c4), null))), | 113 | Matchers.is(new InferredType(Set.of(), Set.of(c1, c4), null))), |
114 | () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE), | 114 | () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE), |
115 | is(new InferredType(Set.of(), Set.of(c3), null))) | 115 | Matchers.is(new InferredType(Set.of(), Set.of(c3), null))) |
116 | ); | 116 | ); |
117 | } | 117 | } |
118 | 118 | ||
@@ -122,8 +122,8 @@ class TypeAnalyzerExampleHierarchyTest { | |||
122 | var a4Result = tester.getPreservedType(a4); | 122 | var a4Result = tester.getPreservedType(a4); |
123 | var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null); | 123 | var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null); |
124 | assertAll( | 124 | assertAll( |
125 | () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), is(expected)), | 125 | () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), Matchers.is(expected)), |
126 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), is(expected)) | 126 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), Matchers.is(expected)) |
127 | ); | 127 | ); |
128 | } | 128 | } |
129 | 129 | ||
@@ -145,9 +145,9 @@ class TypeAnalyzerExampleHierarchyTest { | |||
145 | var c3Result = tester.getPreservedType(c3); | 145 | var c3Result = tester.getPreservedType(c3); |
146 | assertAll( | 146 | assertAll( |
147 | () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE), | 147 | () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE), |
148 | is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))), | 148 | Matchers.is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))), |
149 | () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), | 149 | () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), |
150 | is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null))) | 150 | Matchers.is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null))) |
151 | ); | 151 | ); |
152 | } | 152 | } |
153 | 153 | ||
@@ -158,13 +158,13 @@ class TypeAnalyzerExampleHierarchyTest { | |||
158 | var c1Result = tester.getPreservedType(c1); | 158 | var c1Result = tester.getPreservedType(c1); |
159 | assertAll( | 159 | assertAll( |
160 | () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | 160 | () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE), |
161 | is(new InferredType(Set.of(a1, a4), Set.of(), null))), | 161 | Matchers.is(new InferredType(Set.of(a1, a4), Set.of(), null))), |
162 | () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE), | 162 | () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE), |
163 | is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), | 163 | Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), |
164 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE), | 164 | () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE), |
165 | is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), | 165 | Matchers.is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))), |
166 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | 166 | () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), |
167 | is(new InferredType(Set.of(a1, a4), Set.of(), null))) | 167 | Matchers.is(new InferredType(Set.of(a1, a4), Set.of(), null))) |
168 | ); | 168 | ); |
169 | } | 169 | } |
170 | 170 | ||
@@ -174,9 +174,9 @@ class TypeAnalyzerExampleHierarchyTest { | |||
174 | var a5Result = tester.getPreservedType(a5); | 174 | var a5Result = tester.getPreservedType(a5); |
175 | assertAll( | 175 | assertAll( |
176 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE), | 176 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE), |
177 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | 177 | Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), |
178 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE), | 178 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE), |
179 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))) | 179 | Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))) |
180 | ); | 180 | ); |
181 | } | 181 | } |
182 | 182 | ||
@@ -198,9 +198,9 @@ class TypeAnalyzerExampleHierarchyTest { | |||
198 | var a5Result = tester.getPreservedType(a5); | 198 | var a5Result = tester.getPreservedType(a5); |
199 | assertAll( | 199 | assertAll( |
200 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR), | 200 | () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR), |
201 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | 201 | Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), |
202 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR), | 202 | () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR), |
203 | is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), | 203 | Matchers.is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))), |
204 | () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR), | 204 | () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR), |
205 | is(a5Result.asInferredType())) | 205 | is(a5Result.asInferredType())) |
206 | ); | 206 | ); |
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java deleted file mode 100644 index d0ef9d57..00000000 --- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java +++ /dev/null | |||
@@ -1,205 +0,0 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.Test; | ||
9 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
10 | import tools.refinery.store.representation.TruthValue; | ||
11 | |||
12 | import java.util.LinkedHashMap; | ||
13 | import java.util.Set; | ||
14 | |||
15 | import static org.hamcrest.MatcherAssert.assertThat; | ||
16 | import static org.hamcrest.Matchers.is; | ||
17 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
18 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
19 | |||
20 | class TypeAnalyzerTest { | ||
21 | @Test | ||
22 | void directSupertypesTest() { | ||
23 | var c1 = new PartialRelation("C1", 1); | ||
24 | var c2 = new PartialRelation("C2", 1); | ||
25 | var c3 = new PartialRelation("C3", 1); | ||
26 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
27 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(c2, c3).build()); | ||
28 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build()); | ||
29 | typeInfoMap.put(c3, TypeInfo.builder().build()); | ||
30 | |||
31 | var sut = new TypeAnalyzer(typeInfoMap); | ||
32 | var tester = new TypeAnalyzerTester(sut); | ||
33 | |||
34 | assertAll( | ||
35 | () -> tester.assertConcreteType(c1), | ||
36 | () -> tester.assertConcreteType(c2, c1), | ||
37 | () -> tester.assertConcreteType(c3, c2) | ||
38 | ); | ||
39 | } | ||
40 | |||
41 | @Test | ||
42 | void typeEliminationAbstractToConcreteTest() { | ||
43 | var c1 = new PartialRelation("C1", 1); | ||
44 | var c2 = new PartialRelation("C2", 1); | ||
45 | var a11 = new PartialRelation("A11", 1); | ||
46 | var a12 = new PartialRelation("A12", 1); | ||
47 | var a21 = new PartialRelation("A21", 1); | ||
48 | var a22 = new PartialRelation("A22", 1); | ||
49 | var a3 = new PartialRelation("A3", 1); | ||
50 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
51 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | ||
52 | typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
53 | typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
54 | typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
55 | typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
56 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build()); | ||
57 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build()); | ||
58 | |||
59 | var sut = new TypeAnalyzer(typeInfoMap); | ||
60 | var tester = new TypeAnalyzerTester(sut); | ||
61 | |||
62 | assertAll( | ||
63 | () -> tester.assertConcreteType(c1), | ||
64 | () -> tester.assertConcreteType(c2), | ||
65 | () -> tester.assertEliminatedType(a11, c1), | ||
66 | () -> tester.assertEliminatedType(a12, c1), | ||
67 | () -> tester.assertEliminatedType(a21, c1), | ||
68 | () -> tester.assertEliminatedType(a22, c1), | ||
69 | () -> tester.assertAbstractType(a3, c1, c2) | ||
70 | ); | ||
71 | } | ||
72 | |||
73 | @Test | ||
74 | void typeEliminationConcreteToAbstractTest() { | ||
75 | var c1 = new PartialRelation("C1", 1); | ||
76 | var c2 = new PartialRelation("C2", 1); | ||
77 | var a11 = new PartialRelation("A11", 1); | ||
78 | var a12 = new PartialRelation("A12", 1); | ||
79 | var a21 = new PartialRelation("A21", 1); | ||
80 | var a22 = new PartialRelation("A22", 1); | ||
81 | var a3 = new PartialRelation("A3", 1); | ||
82 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
83 | typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build()); | ||
84 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build()); | ||
85 | typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
86 | typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build()); | ||
87 | typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
88 | typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build()); | ||
89 | typeInfoMap.put(a3, TypeInfo.builder().abstractType().build()); | ||
90 | |||
91 | var sut = new TypeAnalyzer(typeInfoMap); | ||
92 | var tester = new TypeAnalyzerTester(sut); | ||
93 | |||
94 | assertAll( | ||
95 | () -> tester.assertConcreteType(c1), | ||
96 | () -> tester.assertConcreteType(c2), | ||
97 | () -> tester.assertEliminatedType(a11, c1), | ||
98 | () -> tester.assertEliminatedType(a12, c1), | ||
99 | () -> tester.assertEliminatedType(a21, c1), | ||
100 | () -> tester.assertEliminatedType(a22, c1), | ||
101 | () -> tester.assertAbstractType(a3, c1, c2) | ||
102 | ); | ||
103 | } | ||
104 | |||
105 | @Test | ||
106 | void preserveConcreteTypeTest() { | ||
107 | var c1 = new PartialRelation("C1", 1); | ||
108 | var a1 = new PartialRelation("A1", 1); | ||
109 | var c2 = new PartialRelation("C2", 1); | ||
110 | var a2 = new PartialRelation("A2", 1); | ||
111 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
112 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
113 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().supertype(c2).build()); | ||
114 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a2).build()); | ||
115 | typeInfoMap.put(a2, TypeInfo.builder().abstractType().build()); | ||
116 | |||
117 | var sut = new TypeAnalyzer(typeInfoMap); | ||
118 | var tester = new TypeAnalyzerTester(sut); | ||
119 | |||
120 | assertAll( | ||
121 | () -> tester.assertConcreteType(c1), | ||
122 | () -> tester.assertEliminatedType(a1, c1), | ||
123 | () -> tester.assertConcreteType(c2, c1), | ||
124 | () -> tester.assertEliminatedType(a2, c2) | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | @Test | ||
129 | void mostGeneralCurrentTypeTest() { | ||
130 | var c1 = new PartialRelation("C1", 1); | ||
131 | var c2 = new PartialRelation("C2", 1); | ||
132 | var c3 = new PartialRelation("C3", 1); | ||
133 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
134 | typeInfoMap.put(c1, TypeInfo.builder().supertype(c3).build()); | ||
135 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build()); | ||
136 | typeInfoMap.put(c3, TypeInfo.builder().build()); | ||
137 | |||
138 | var sut = new TypeAnalyzer(typeInfoMap); | ||
139 | var tester = new TypeAnalyzerTester(sut); | ||
140 | var c3Result = tester.getPreservedType(c3); | ||
141 | |||
142 | var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3); | ||
143 | assertAll( | ||
144 | () -> assertThat(tester.getInferredType(c3), is(expected)), | ||
145 | () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(expected)) | ||
146 | ); | ||
147 | } | ||
148 | |||
149 | @Test | ||
150 | void preferFirstConcreteTypeTest() { | ||
151 | var a1 = new PartialRelation("A1", 1); | ||
152 | var c1 = new PartialRelation("C1", 1); | ||
153 | var c2 = new PartialRelation("C2", 1); | ||
154 | var c3 = new PartialRelation("C3", 1); | ||
155 | var c4 = new PartialRelation("C4", 1); | ||
156 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
157 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
158 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build()); | ||
159 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build()); | ||
160 | typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build()); | ||
161 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | ||
162 | |||
163 | var sut = new TypeAnalyzer(typeInfoMap); | ||
164 | var tester = new TypeAnalyzerTester(sut); | ||
165 | var c1Result = tester.getPreservedType(c1); | ||
166 | var a1Result = tester.getPreservedType(a1); | ||
167 | |||
168 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
169 | is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2))); | ||
170 | } | ||
171 | |||
172 | @Test | ||
173 | void preferFirstMostGeneralConcreteTypeTest() { | ||
174 | var a1 = new PartialRelation("A1", 1); | ||
175 | var c1 = new PartialRelation("C1", 1); | ||
176 | var c2 = new PartialRelation("C2", 1); | ||
177 | var c3 = new PartialRelation("C3", 1); | ||
178 | var c4 = new PartialRelation("C4", 1); | ||
179 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
180 | typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build()); | ||
181 | typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build()); | ||
182 | typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build()); | ||
183 | typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build()); | ||
184 | typeInfoMap.put(a1, TypeInfo.builder().abstractType().build()); | ||
185 | |||
186 | var sut = new TypeAnalyzer(typeInfoMap); | ||
187 | var tester = new TypeAnalyzerTester(sut); | ||
188 | var c1Result = tester.getPreservedType(c1); | ||
189 | var a1Result = tester.getPreservedType(a1); | ||
190 | |||
191 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
192 | is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3))); | ||
193 | } | ||
194 | |||
195 | @Test | ||
196 | void circularTypeHierarchyTest() { | ||
197 | var c1 = new PartialRelation("C1", 1); | ||
198 | var c2 = new PartialRelation("C2", 1); | ||
199 | var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>(); | ||
200 | typeInfoMap.put(c1, TypeInfo.builder().supertype(c2).build()); | ||
201 | typeInfoMap.put(c2, TypeInfo.builder().supertype(c1).build()); | ||
202 | |||
203 | assertThrows(IllegalArgumentException.class, () -> new TypeAnalyzer(typeInfoMap)); | ||
204 | } | ||
205 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java new file mode 100644 index 00000000..cd9df19a --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java | |||
@@ -0,0 +1,186 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.junit.jupiter.api.BeforeEach; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.model.Model; | ||
11 | import tools.refinery.store.model.ModelStore; | ||
12 | import tools.refinery.store.query.ModelQueryAdapter; | ||
13 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
14 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
15 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
16 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
18 | import tools.refinery.store.reasoning.seed.ModelSeed; | ||
19 | import tools.refinery.store.representation.TruthValue; | ||
20 | import tools.refinery.store.tuple.Tuple; | ||
21 | |||
22 | import static org.hamcrest.MatcherAssert.assertThat; | ||
23 | import static org.hamcrest.Matchers.is; | ||
24 | |||
25 | class TypeHierarchyPartialModelTest { | ||
26 | private final PartialRelation person = new PartialRelation("Person", 1); | ||
27 | private final PartialRelation member = new PartialRelation("Member", 1); | ||
28 | private final PartialRelation student = new PartialRelation("Student", 1); | ||
29 | private final PartialRelation teacher = new PartialRelation("Teacher", 1); | ||
30 | private final PartialRelation pet = new PartialRelation("Pet", 1); | ||
31 | |||
32 | private Model model; | ||
33 | |||
34 | @BeforeEach | ||
35 | void beforeEach() { | ||
36 | var typeHierarchy = TypeHierarchy.builder() | ||
37 | .type(person, true) | ||
38 | .type(member, true, person) | ||
39 | .type(student, member) | ||
40 | .type(teacher, member) | ||
41 | .type(pet) | ||
42 | .build(); | ||
43 | |||
44 | var store = ModelStore.builder() | ||
45 | .with(ViatraModelQueryAdapter.builder()) | ||
46 | .with(ReasoningAdapter.builder()) | ||
47 | .with(new TypeHierarchyTranslator(typeHierarchy)) | ||
48 | .build(); | ||
49 | |||
50 | var seed = ModelSeed.builder(4) | ||
51 | .seed(person, builder -> builder | ||
52 | .reducedValue(TruthValue.UNKNOWN) | ||
53 | .put(Tuple.of(3), TruthValue.FALSE)) | ||
54 | .seed(member, builder -> builder | ||
55 | .reducedValue(TruthValue.UNKNOWN) | ||
56 | .put(Tuple.of(1), TruthValue.TRUE) | ||
57 | .put(Tuple.of(2), TruthValue.TRUE)) | ||
58 | .seed(student, builder -> builder | ||
59 | .reducedValue(TruthValue.UNKNOWN) | ||
60 | .put(Tuple.of(0), TruthValue.TRUE) | ||
61 | .put(Tuple.of(2), TruthValue.FALSE)) | ||
62 | .seed(teacher, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
63 | .seed(pet, builder -> builder.reducedValue(TruthValue.UNKNOWN)) | ||
64 | .build(); | ||
65 | model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(seed); | ||
66 | } | ||
67 | |||
68 | @Test | ||
69 | void initialModelTest() { | ||
70 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
71 | |||
72 | var personInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, person); | ||
73 | assertThat(personInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
74 | assertThat(personInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
75 | assertThat(personInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
76 | assertThat(personInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
77 | |||
78 | var memberInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, member); | ||
79 | assertThat(memberInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
80 | assertThat(memberInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
81 | assertThat(memberInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
82 | assertThat(memberInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
83 | |||
84 | var studentInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, student); | ||
85 | assertThat(studentInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
86 | assertThat(studentInterpretation.get(Tuple.of(1)), is(TruthValue.UNKNOWN)); | ||
87 | assertThat(studentInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE)); | ||
88 | assertThat(studentInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
89 | |||
90 | var teacherInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, teacher); | ||
91 | assertThat(teacherInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
92 | assertThat(teacherInterpretation.get(Tuple.of(1)), is(TruthValue.UNKNOWN)); | ||
93 | assertThat(teacherInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
94 | assertThat(teacherInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
95 | |||
96 | var petInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, pet); | ||
97 | assertThat(petInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
98 | assertThat(petInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
99 | assertThat(petInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE)); | ||
100 | assertThat(petInterpretation.get(Tuple.of(3)), is(TruthValue.UNKNOWN)); | ||
101 | } | ||
102 | |||
103 | @Test | ||
104 | void initialModelCandidateTest() { | ||
105 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
106 | |||
107 | var personCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, person); | ||
108 | assertThat(personCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
109 | assertThat(personCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
110 | assertThat(personCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
111 | assertThat(personCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
112 | |||
113 | var memberCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, member); | ||
114 | assertThat(memberCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
115 | assertThat(memberCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
116 | assertThat(memberCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
117 | assertThat(memberCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
118 | |||
119 | var studentCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, student); | ||
120 | assertThat(studentCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.TRUE)); | ||
121 | assertThat(studentCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
122 | assertThat(studentCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE)); | ||
123 | assertThat(studentCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
124 | |||
125 | var teacherCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, teacher); | ||
126 | assertThat(teacherCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
127 | assertThat(teacherCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
128 | assertThat(teacherCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.TRUE)); | ||
129 | assertThat(teacherCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
130 | |||
131 | var petCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, pet); | ||
132 | assertThat(petCandidateInterpretation.get(Tuple.of(0)), is(TruthValue.FALSE)); | ||
133 | assertThat(petCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
134 | assertThat(petCandidateInterpretation.get(Tuple.of(2)), is(TruthValue.FALSE)); | ||
135 | assertThat(petCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
136 | } | ||
137 | |||
138 | @Test | ||
139 | void refinedModelTest() { | ||
140 | var reasoningAdapter = model.getAdapter(ReasoningAdapter.class); | ||
141 | var studentRefiner = reasoningAdapter.getRefiner(student); | ||
142 | studentRefiner.merge(Tuple.of(1), TruthValue.FALSE); | ||
143 | studentRefiner.merge(Tuple.of(3), TruthValue.TRUE); | ||
144 | model.getAdapter(ModelQueryAdapter.class).flushChanges(); | ||
145 | |||
146 | var personInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, person); | ||
147 | assertThat(personInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
148 | assertThat(personInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR)); | ||
149 | |||
150 | var personCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, person); | ||
151 | assertThat(personCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
152 | assertThat(personCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
153 | |||
154 | var memberInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, member); | ||
155 | assertThat(memberInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
156 | assertThat(memberInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR)); | ||
157 | |||
158 | var memberCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, member); | ||
159 | assertThat(memberCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
160 | assertThat(memberCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
161 | |||
162 | var studentInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, student); | ||
163 | assertThat(studentInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
164 | assertThat(studentInterpretation.get(Tuple.of(3)), is(TruthValue.ERROR)); | ||
165 | |||
166 | var studentCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, student); | ||
167 | assertThat(studentCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
168 | assertThat(studentCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
169 | |||
170 | var teacherInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, teacher); | ||
171 | assertThat(teacherInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
172 | assertThat(teacherInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
173 | |||
174 | var teacherCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, teacher); | ||
175 | assertThat(teacherCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.TRUE)); | ||
176 | assertThat(teacherCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
177 | |||
178 | var petInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.PARTIAL, pet); | ||
179 | assertThat(petInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
180 | assertThat(petInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
181 | |||
182 | var petCandidateInterpretation = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, pet); | ||
183 | assertThat(petCandidateInterpretation.get(Tuple.of(1)), is(TruthValue.FALSE)); | ||
184 | assertThat(petCandidateInterpretation.get(Tuple.of(3)), is(TruthValue.FALSE)); | ||
185 | } | ||
186 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java new file mode 100644 index 00000000..931c62dd --- /dev/null +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java | |||
@@ -0,0 +1,224 @@ | |||
1 | /* | ||
2 | * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> | ||
3 | * | ||
4 | * SPDX-License-Identifier: EPL-2.0 | ||
5 | */ | ||
6 | package tools.refinery.store.reasoning.translator.typehierarchy; | ||
7 | |||
8 | import org.hamcrest.Matchers; | ||
9 | import org.junit.jupiter.api.Test; | ||
10 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
11 | import tools.refinery.store.reasoning.translator.TranslationException; | ||
12 | import tools.refinery.store.representation.TruthValue; | ||
13 | |||
14 | import java.util.Set; | ||
15 | |||
16 | import static org.hamcrest.MatcherAssert.assertThat; | ||
17 | import static org.hamcrest.Matchers.hasEntry; | ||
18 | import static org.junit.jupiter.api.Assertions.assertAll; | ||
19 | import static org.junit.jupiter.api.Assertions.assertThrows; | ||
20 | |||
21 | class TypeHierarchyTest { | ||
22 | @Test | ||
23 | void directSupertypesTest() { | ||
24 | var c1 = new PartialRelation("C1", 1); | ||
25 | var c2 = new PartialRelation("C2", 1); | ||
26 | var c3 = new PartialRelation("C3", 1); | ||
27 | |||
28 | var sut = TypeHierarchy.builder() | ||
29 | .type(c1, c2, c3) | ||
30 | .type(c2, c3) | ||
31 | .type(c3) | ||
32 | .build(); | ||
33 | var tester = new TypeHierarchyTester(sut); | ||
34 | |||
35 | assertAll( | ||
36 | () -> tester.assertConcreteType(c1), | ||
37 | () -> tester.assertConcreteType(c2, c1), | ||
38 | () -> tester.assertConcreteType(c3, c2) | ||
39 | ); | ||
40 | } | ||
41 | |||
42 | @Test | ||
43 | void typeEliminationAbstractToConcreteTest() { | ||
44 | var c1 = new PartialRelation("C1", 1); | ||
45 | var c2 = new PartialRelation("C2", 1); | ||
46 | var a11 = new PartialRelation("A11", 1); | ||
47 | var a12 = new PartialRelation("A12", 1); | ||
48 | var a21 = new PartialRelation("A21", 1); | ||
49 | var a22 = new PartialRelation("A22", 1); | ||
50 | var a3 = new PartialRelation("A3", 1); | ||
51 | |||
52 | var sut = TypeHierarchy.builder() | ||
53 | .type(a3, true) | ||
54 | .type(a21, true, a3) | ||
55 | .type(a22, true, a3) | ||
56 | .type(a11, true, a21, a22) | ||
57 | .type(a12, true, a21, a22) | ||
58 | .type(c1, a11, a12) | ||
59 | .type(c2, a3) | ||
60 | .build(); | ||
61 | var tester = new TypeHierarchyTester(sut); | ||
62 | |||
63 | assertAll( | ||
64 | () -> tester.assertConcreteType(c1), | ||
65 | () -> tester.assertConcreteType(c2), | ||
66 | () -> tester.assertEliminatedType(a11, c1), | ||
67 | () -> tester.assertEliminatedType(a12, c1), | ||
68 | () -> tester.assertEliminatedType(a21, c1), | ||
69 | () -> tester.assertEliminatedType(a22, c1), | ||
70 | () -> tester.assertAbstractType(a3, c1, c2) | ||
71 | ); | ||
72 | } | ||
73 | |||
74 | @Test | ||
75 | void typeEliminationConcreteToAbstractTest() { | ||
76 | var c1 = new PartialRelation("C1", 1); | ||
77 | var c2 = new PartialRelation("C2", 1); | ||
78 | var a11 = new PartialRelation("A11", 1); | ||
79 | var a12 = new PartialRelation("A12", 1); | ||
80 | var a21 = new PartialRelation("A21", 1); | ||
81 | var a22 = new PartialRelation("A22", 1); | ||
82 | var a3 = new PartialRelation("A3", 1); | ||
83 | |||
84 | var sut = TypeHierarchy.builder() | ||
85 | .type(c1, a11, a12) | ||
86 | .type(c2, a3) | ||
87 | .type(a11, true, a21, a22) | ||
88 | .type(a12, true, a21, a22) | ||
89 | .type(a21, true, a3) | ||
90 | .type(a22, true, a3) | ||
91 | .type(a3, true) | ||
92 | .build(); | ||
93 | var tester = new TypeHierarchyTester(sut); | ||
94 | |||
95 | assertAll( | ||
96 | () -> tester.assertConcreteType(c1), | ||
97 | () -> tester.assertConcreteType(c2), | ||
98 | () -> tester.assertEliminatedType(a11, c1), | ||
99 | () -> tester.assertEliminatedType(a12, c1), | ||
100 | () -> tester.assertEliminatedType(a21, c1), | ||
101 | () -> tester.assertEliminatedType(a22, c1), | ||
102 | () -> tester.assertAbstractType(a3, c1, c2) | ||
103 | ); | ||
104 | } | ||
105 | |||
106 | @Test | ||
107 | void preserveConcreteTypeTest() { | ||
108 | var c1 = new PartialRelation("C1", 1); | ||
109 | var a1 = new PartialRelation("A1", 1); | ||
110 | var c2 = new PartialRelation("C2", 1); | ||
111 | var a2 = new PartialRelation("A2", 1); | ||
112 | |||
113 | var sut = TypeHierarchy.builder() | ||
114 | .type(c1, a1) | ||
115 | .type(a1, true, c2) | ||
116 | .type(c2, a2) | ||
117 | .type(a2, true) | ||
118 | .build(); | ||
119 | var tester = new TypeHierarchyTester(sut); | ||
120 | |||
121 | assertAll( | ||
122 | () -> tester.assertConcreteType(c1), | ||
123 | () -> tester.assertEliminatedType(a1, c1), | ||
124 | () -> tester.assertConcreteType(c2, c1), | ||
125 | () -> tester.assertEliminatedType(a2, c2) | ||
126 | ); | ||
127 | } | ||
128 | |||
129 | @Test | ||
130 | void mostGeneralCurrentTypeTest() { | ||
131 | var c1 = new PartialRelation("C1", 1); | ||
132 | var c2 = new PartialRelation("C2", 1); | ||
133 | var c3 = new PartialRelation("C3", 1); | ||
134 | |||
135 | var sut = TypeHierarchy.builder() | ||
136 | .type(c1, c3) | ||
137 | .type(c2, c3) | ||
138 | .type(c3) | ||
139 | .build(); | ||
140 | var tester = new TypeHierarchyTester(sut); | ||
141 | var c3Result = tester.getPreservedType(c3); | ||
142 | |||
143 | var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3); | ||
144 | assertAll( | ||
145 | () -> assertThat(tester.getInferredType(c3), Matchers.is(expected)), | ||
146 | () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), Matchers.is(expected)) | ||
147 | ); | ||
148 | } | ||
149 | |||
150 | @Test | ||
151 | void preferFirstConcreteTypeTest() { | ||
152 | var a1 = new PartialRelation("A1", 1); | ||
153 | var c1 = new PartialRelation("C1", 1); | ||
154 | var c2 = new PartialRelation("C2", 1); | ||
155 | var c3 = new PartialRelation("C3", 1); | ||
156 | var c4 = new PartialRelation("C4", 1); | ||
157 | |||
158 | var sut = TypeHierarchy.builder() | ||
159 | .type(c1, a1) | ||
160 | .type(c2, a1) | ||
161 | .type(c3, a1) | ||
162 | .type(c4, c3) | ||
163 | .type(a1, true) | ||
164 | .build(); | ||
165 | var tester = new TypeHierarchyTester(sut); | ||
166 | var c1Result = tester.getPreservedType(c1); | ||
167 | var a1Result = tester.getPreservedType(a1); | ||
168 | |||
169 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
170 | Matchers.is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2))); | ||
171 | } | ||
172 | |||
173 | @Test | ||
174 | void preferFirstMostGeneralConcreteTypeTest() { | ||
175 | var a1 = new PartialRelation("A1", 1); | ||
176 | var c1 = new PartialRelation("C1", 1); | ||
177 | var c2 = new PartialRelation("C2", 1); | ||
178 | var c3 = new PartialRelation("C3", 1); | ||
179 | var c4 = new PartialRelation("C4", 1); | ||
180 | |||
181 | var sut = TypeHierarchy.builder() | ||
182 | .type(c4, c3) | ||
183 | .type(c3, a1) | ||
184 | .type(c2, a1) | ||
185 | .type(c1, a1) | ||
186 | .type(a1, true) | ||
187 | .build(); | ||
188 | var tester = new TypeHierarchyTester(sut); | ||
189 | var c1Result = tester.getPreservedType(c1); | ||
190 | var a1Result = tester.getPreservedType(a1); | ||
191 | |||
192 | assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE), | ||
193 | Matchers.is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3))); | ||
194 | } | ||
195 | |||
196 | @Test | ||
197 | void circularTypeHierarchyTest() { | ||
198 | var c1 = new PartialRelation("C1", 1); | ||
199 | var c2 = new PartialRelation("C2", 1); | ||
200 | var builder = TypeHierarchy.builder() | ||
201 | .type(c1, c2) | ||
202 | .type(c2, c1); | ||
203 | |||
204 | assertThrows(TranslationException.class, builder::build); | ||
205 | } | ||
206 | |||
207 | @Test | ||
208 | void chainedEliminationTest() { | ||
209 | var a1 = new PartialRelation("A1", 1); | ||
210 | var a2 = new PartialRelation("A2", 1); | ||
211 | var c1 = new PartialRelation("C1", 1); | ||
212 | |||
213 | var sut = TypeHierarchy.builder() | ||
214 | .type(a1, true) | ||
215 | .type(a2, true, a1) | ||
216 | .type(c1, a2) | ||
217 | .build(); | ||
218 | |||
219 | assertAll( | ||
220 | () -> assertThat(sut.getEliminatedTypes(), hasEntry(a1, c1)), | ||
221 | () -> assertThat(sut.getEliminatedTypes(), hasEntry(a2, c1)) | ||
222 | ); | ||
223 | } | ||
224 | } | ||
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTester.java index 2924816e..647bd782 100644 --- a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java +++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTester.java | |||
@@ -11,10 +11,10 @@ import static org.hamcrest.MatcherAssert.assertThat; | |||
11 | import static org.hamcrest.Matchers.*; | 11 | import static org.hamcrest.Matchers.*; |
12 | import static org.hamcrest.Matchers.is; | 12 | import static org.hamcrest.Matchers.is; |
13 | 13 | ||
14 | class TypeAnalyzerTester { | 14 | class TypeHierarchyTester { |
15 | private final TypeAnalyzer sut; | 15 | private final TypeHierarchy sut; |
16 | 16 | ||
17 | public TypeAnalyzerTester(TypeAnalyzer sut) { | 17 | public TypeHierarchyTester(TypeHierarchy sut) { |
18 | this.sut = sut; | 18 | this.sut = sut; |
19 | } | 19 | } |
20 | 20 | ||
@@ -32,22 +32,21 @@ class TypeAnalyzerTester { | |||
32 | 32 | ||
33 | private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous, | 33 | private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous, |
34 | PartialRelation... directSubtypes) { | 34 | PartialRelation... directSubtypes) { |
35 | var result = sut.getAnalysisResults().get(partialRelation); | 35 | var result = sut.getPreservedTypes().get(partialRelation); |
36 | assertThat(result, is(instanceOf(PreservedType.class))); | 36 | assertThat(result, not(nullValue())); |
37 | var preservedResult = (PreservedType) result; | 37 | assertThat(result.isAbstractType(), is(isAbstract)); |
38 | assertThat(preservedResult.isAbstractType(), is(isAbstract)); | 38 | assertThat(result.isVacuous(), is(isVacuous)); |
39 | assertThat(preservedResult.isVacuous(), is(isVacuous)); | 39 | assertThat(result.getDirectSubtypes(), hasItems(directSubtypes)); |
40 | assertThat(preservedResult.getDirectSubtypes(), hasItems(directSubtypes)); | ||
41 | } | 40 | } |
42 | 41 | ||
43 | public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) { | 42 | public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) { |
44 | var result = sut.getAnalysisResults().get(partialRelation); | 43 | var result = sut.getEliminatedTypes().get(partialRelation); |
45 | assertThat(result, is(instanceOf(EliminatedType.class))); | 44 | assertThat(result, not(nullValue())); |
46 | assertThat(((EliminatedType) result).replacement(), is(replacement)); | 45 | assertThat(result, is(replacement)); |
47 | } | 46 | } |
48 | 47 | ||
49 | public PreservedType getPreservedType(PartialRelation partialRelation) { | 48 | public TypeAnalysisResult getPreservedType(PartialRelation partialRelation) { |
50 | return (PreservedType) sut.getAnalysisResults().get(partialRelation); | 49 | return sut.getPreservedTypes().get(partialRelation); |
51 | } | 50 | } |
52 | 51 | ||
53 | public InferredType getInferredType(PartialRelation partialRelation) { | 52 | public InferredType getInferredType(PartialRelation partialRelation) { |