aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-reasoning/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/store-reasoning/src')
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java25
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java43
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java41
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java10
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/CleanupActionLiteral.java43
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/FocusActionLiteral.java48
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/MergeActionLiteral.java60
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/actions/PartialActionLiterals.java42
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialClauseRewriter.java223
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/PartialQueryRewriter.java53
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java186
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java153
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java92
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AbstractPartialInterpretation.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/AnyPartialInterpretation.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java)8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialInterpretation.java34
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/PartialRelationRewriter.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationInterpretationFactory.java195
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/interpretation/QueryBasedRelationRewriter.java63
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ClauseLifter.java182
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java136
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Concreteness.java18
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateLowerBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountCandidateUpperBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountLowerBoundLiteral.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/CountUpperBoundLiteral.java51
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java19
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AbstractPartialInterpretationRefiner.java29
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/AnyPartialInterpretationRefiner.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/ConcreteSymbolRefiner.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/DefaultStorageRefiner.java79
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialInterpretationRefiner.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/PartialModelInitializer.java14
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/RefinementBasedInitializer.java34
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/refinement/StorageRefiner.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java5
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java8
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/MapBasedSeed.java111
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/ModelSeed.java95
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java59
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/SeedInitializer.java28
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java27
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java159
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java30
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AnyPartialSymbolTranslator.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialRelationTranslator.java390
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/PartialSymbolTranslator.java212
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/RoundingMode.java12
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java27
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationException.java35
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java93
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java54
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslator.java255
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentInfo.java24
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ContainmentLinkRefiner.java128
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainmentLinkView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/ForbiddenContainsView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainment.java77
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/InferredContainmentLinkView.java35
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainmentLinkView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/containment/MustContainsView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/CrossReferenceUtils.java57
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceInfo.java16
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceRefiner.java46
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/DirectedCrossReferenceTranslator.java94
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceInfo.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceRefiner.java44
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/crossreference/UndirectedCrossReferenceTranslator.java83
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ContainedTypeHierarchyBuilder.java33
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/Metamodel.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilder.java225
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTranslator.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfo.java13
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRefiner.java64
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/EqualsRelationRewriter.java85
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/ExistsRefiner.java55
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/LowerCardinalityView.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectInitializer.java135
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectStorageRefiner.java45
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiObjectTranslator.java107
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/MultiView.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiobject/UpperCardinalityView.java23
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/ConstrainedMultiplicity.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/InvalidMultiplicityErrorTranslator.java141
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/Multiplicity.java14
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/multiplicity/UnconstrainedMultiplicity.java28
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeInterpretation.java77
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRefiner.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeRelationTranslator.java62
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/opposite/OppositeUtils.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/predicate/PredicateTranslator.java93
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/proxy/PartialRelationTranslatorProxy.java52
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/CandidateTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeView.java40
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java51
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeRefiner.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeView.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeView.java)21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MayTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/MustTypeView.java21
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java141
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java138
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchy.java (renamed from subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java)109
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyBuilder.java66
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyInitializer.java64
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslator.java111
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java44
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/PartialModelTest.java108
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/lifting/DnfLifterTest.java395
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/containment/ContainmentHierarchyTranslatorTest.java128
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelBuilderTest.java58
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/metamodel/MetamodelTest.java152
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/multiobject/PartialCountTest.java321
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/ConcreteSupertypeTest.java145
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisExampleHierarchyTest.java (renamed from subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java)92
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java205
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyPartialModelTest.java186
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTest.java224
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTester.java (renamed from subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java)27
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 */
6package tools.refinery.store.reasoning;
7
8public 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 */
6package tools.refinery.store.reasoning;
7
8import tools.refinery.store.reasoning.representation.PartialSymbol;
9import tools.refinery.store.map.Cursor;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning;
7 7
8import org.jetbrains.annotations.Nullable;
8import tools.refinery.store.adapter.ModelAdapter; 9import tools.refinery.store.adapter.ModelAdapter;
9import tools.refinery.store.query.resultset.ResultSet; 10import tools.refinery.store.reasoning.internal.ReasoningBuilderImpl;
10import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation;
12import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Concreteness;
14import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner;
15import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 16import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.reasoning.representation.PartialRelation; 17import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.representation.PartialSymbol; 18import tools.refinery.store.reasoning.representation.PartialSymbol;
19import tools.refinery.store.tuple.Tuple1;
14 20
15public interface ReasoningAdapter extends ModelAdapter { 21public 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 @@
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning;
7 7
8import tools.refinery.store.adapter.ModelAdapterBuilder; 8import tools.refinery.store.adapter.ModelAdapterBuilder;
9import tools.refinery.store.dse.transition.objectives.Objective;
9import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.reasoning.literal.Modality;
11import tools.refinery.store.query.dnf.Dnf; 11import tools.refinery.store.query.dnf.Dnf;
12import tools.refinery.store.query.dnf.FunctionalQuery;
13import tools.refinery.store.query.dnf.Query;
14import tools.refinery.store.query.dnf.RelationalQuery;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
18import tools.refinery.store.reasoning.refinement.StorageRefiner;
19import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator;
20import tools.refinery.store.representation.Symbol;
12 21
13import java.util.Collection; 22import java.util.Collection;
14import java.util.List; 23import java.util.List;
15 24
16@SuppressWarnings("UnusedReturnValue") 25@SuppressWarnings("UnusedReturnValue")
17public interface ReasoningBuilder extends ModelAdapterBuilder { 26public 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
8import tools.refinery.store.adapter.ModelStoreAdapter; 8import tools.refinery.store.adapter.ModelStoreAdapter;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.literal.Concreteness;
10import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 11import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11import tools.refinery.store.query.dnf.Dnf; 12import tools.refinery.store.reasoning.seed.ModelSeed;
12 13
13import java.util.Collection; 14import java.util.Collection;
15import java.util.Set;
14 16
15public interface ReasoningStoreAdapter extends ModelStoreAdapter { 17public 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 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
9import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.tuple.Tuple;
14
15import java.util.List;
16
17public 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 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
9import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13
14import java.util.List;
15
16public 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 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.dse.transition.actions.AbstractActionLiteral;
9import tools.refinery.store.dse.transition.actions.BoundActionLiteral;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.representation.PartialSymbol;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.List;
17
18public 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 */
6package tools.refinery.store.reasoning.actions;
7
8import tools.refinery.store.query.term.NodeVariable;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.TruthValue;
12
13import java.util.List;
14
15public 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 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.DnfBuilder;
11import tools.refinery.store.query.dnf.DnfClause;
12import tools.refinery.store.query.literal.AbstractCallLiteral;
13import tools.refinery.store.query.literal.AbstractCountLiteral;
14import tools.refinery.store.query.literal.CallPolarity;
15import tools.refinery.store.query.literal.Literal;
16import tools.refinery.store.query.term.Aggregator;
17import tools.refinery.store.query.term.ConstantTerm;
18import tools.refinery.store.query.term.Term;
19import tools.refinery.store.query.term.Variable;
20import tools.refinery.store.query.term.int_.IntTerms;
21import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
22import tools.refinery.store.reasoning.ReasoningAdapter;
23import tools.refinery.store.reasoning.literal.*;
24import tools.refinery.store.reasoning.representation.PartialRelation;
25import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
26import tools.refinery.store.representation.cardinality.UpperCardinalities;
27
28import java.util.*;
29import java.util.function.BinaryOperator;
30
31class 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 */
6package tools.refinery.store.reasoning.internal;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.query.rewriter.AbstractRecursiveRewriter;
10import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
11import tools.refinery.store.reasoning.lifting.DnfLifter;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13
14import java.util.HashMap;
15import java.util.Map;
16
17class 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 */
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.model.Interpretation;
8import tools.refinery.store.model.Model; 10import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter; 11import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.PartialInterpretation; 12import tools.refinery.store.reasoning.interpretation.AnyPartialInterpretation;
13import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.refinement.AnyPartialInterpretationRefiner;
16import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
17import tools.refinery.store.reasoning.refinement.StorageRefiner;
18import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11import tools.refinery.store.reasoning.representation.PartialSymbol; 19import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.query.dnf.Dnf; 20import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
13import tools.refinery.store.query.resultset.ResultSet; 21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.cardinality.CardinalityInterval;
23import tools.refinery.store.representation.cardinality.CardinalityIntervals;
24import tools.refinery.store.tuple.Tuple;
25import tools.refinery.store.tuple.Tuple1;
14 26
15public class ReasoningAdapterImpl implements ReasoningAdapter { 27import java.util.HashMap;
28import java.util.Map;
29
30class 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 @@
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 8import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.objectives.Objective;
11import tools.refinery.store.dse.transition.objectives.Objectives;
9import tools.refinery.store.model.ModelStore; 12import tools.refinery.store.model.ModelStore;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.query.ModelQueryBuilder;
10import tools.refinery.store.query.dnf.Dnf; 15import tools.refinery.store.query.dnf.Dnf;
16import tools.refinery.store.query.dnf.FunctionalQuery;
17import tools.refinery.store.query.dnf.Query;
18import tools.refinery.store.query.dnf.RelationalQuery;
11import tools.refinery.store.reasoning.ReasoningBuilder; 19import tools.refinery.store.reasoning.ReasoningBuilder;
20import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
21import tools.refinery.store.reasoning.lifting.DnfLifter;
22import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.literal.Modality; 23import tools.refinery.store.reasoning.literal.Modality;
24import tools.refinery.store.reasoning.refinement.DefaultStorageRefiner;
25import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
26import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
27import tools.refinery.store.reasoning.refinement.StorageRefiner;
28import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
29import tools.refinery.store.reasoning.translator.AnyPartialSymbolTranslator;
30import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
31import tools.refinery.store.representation.AnySymbol;
32import tools.refinery.store.representation.Symbol;
33import tools.refinery.store.statecoding.StateCoderBuilder;
34
35import java.util.*;
13 36
14public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder<ReasoningStoreAdapterImpl> 37public 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 */
6package tools.refinery.store.reasoning.internal; 6package tools.refinery.store.reasoning.internal;
7 7
8import tools.refinery.store.reasoning.ReasoningStoreAdapter; 8import tools.refinery.store.dse.propagation.PropagationAdapter;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.ModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningStoreAdapter;
13import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
16import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
17import tools.refinery.store.reasoning.refinement.StorageRefiner;
11import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 18import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
12import tools.refinery.store.query.dnf.Dnf; 19import tools.refinery.store.reasoning.seed.ModelSeed;
20import tools.refinery.store.representation.AnySymbol;
21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.tuple.Tuple;
13 23
14import java.util.Collection; 24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27import java.util.Set;
15 28
16public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter { 29class 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 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.literal.Concreteness;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11
12public 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 */
6package tools.refinery.store.reasoning; 6package tools.refinery.store.reasoning.interpretation;
7 7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.literal.Concreteness;
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol; 10import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9 11
10public sealed interface AnyPartialInterpretation permits PartialInterpretation { 12public 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 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.tuple.Tuple;
14
15import java.util.Set;
16
17public 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 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.query.literal.AbstractCallLiteral;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.Variable;
11import tools.refinery.store.reasoning.literal.Concreteness;
12import tools.refinery.store.reasoning.literal.Modality;
13
14import java.util.List;
15import java.util.Set;
16
17@FunctionalInterface
18public 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 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ModelQueryBuilder;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.resultset.ResultSet;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.representation.PartialSymbol;
17import tools.refinery.store.representation.TruthValue;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.Set;
21
22public 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 */
6package tools.refinery.store.reasoning.interpretation;
7
8import tools.refinery.store.query.dnf.RelationalQuery;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.reasoning.literal.Concreteness;
13import tools.refinery.store.reasoning.literal.Modality;
14
15import java.util.List;
16import java.util.Set;
17
18public 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 */
6package tools.refinery.store.reasoning.lifting;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.dnf.Dnf;
10import tools.refinery.store.query.dnf.DnfClause;
11import tools.refinery.store.query.literal.*;
12import tools.refinery.store.query.term.NodeVariable;
13import tools.refinery.store.query.term.ParameterDirection;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.reasoning.ReasoningAdapter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.literal.ModalConstraint;
18import tools.refinery.store.reasoning.literal.Modality;
19
20import java.util.*;
21import java.util.stream.Collectors;
22
23class 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 */
6package tools.refinery.store.reasoning.lifting; 6package tools.refinery.store.reasoning.lifting;
7 7
8import org.jetbrains.annotations.Nullable; 8import tools.refinery.store.query.dnf.*;
9import tools.refinery.store.query.dnf.Dnf; 9import tools.refinery.store.query.equality.DnfEqualityChecker;
10import tools.refinery.store.query.dnf.DnfBuilder;
11import tools.refinery.store.query.dnf.DnfClause;
12import tools.refinery.store.query.literal.CallLiteral;
13import tools.refinery.store.query.literal.CallPolarity;
14import tools.refinery.store.query.literal.Literal; 10import tools.refinery.store.query.literal.Literal;
15import tools.refinery.store.query.term.NodeVariable; 11import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.reasoning.ReasoningAdapter;
18import tools.refinery.store.reasoning.literal.ModalConstraint;
19import tools.refinery.store.reasoning.literal.Modality; 12import tools.refinery.store.reasoning.literal.Modality;
20import tools.refinery.store.reasoning.literal.PartialLiterals;
21import tools.refinery.store.util.CycleDetectingMapper;
22 13
23import java.util.ArrayList; 14import java.util.HashMap;
24import java.util.LinkedHashSet;
25import java.util.List; 15import java.util.List;
16import java.util.Map;
26 17
27public class DnfLifter { 18public 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 */
6package tools.refinery.store.reasoning.lifting;
7
8import tools.refinery.store.query.dnf.Dnf;
9import tools.refinery.store.reasoning.literal.Modality;
10
11record 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 */
6package tools.refinery.store.reasoning.literal;
7
8import java.util.Locale;
9
10public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15
16import java.util.List;
17
18public 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 */
6package tools.refinery.store.reasoning.literal;
7
8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.literal.AbstractCallLiteral;
10import tools.refinery.store.query.literal.AbstractCountLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.substitution.Substitution;
13import tools.refinery.store.query.term.DataVariable;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.representation.cardinality.UpperCardinalities;
16import tools.refinery.store.representation.cardinality.UpperCardinality;
17
18import java.util.List;
19
20public 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 @@
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.Constraint; 8import tools.refinery.store.query.Constraint;
9import tools.refinery.store.query.InvalidQueryException;
9import tools.refinery.store.query.equality.LiteralEqualityHelper; 10import tools.refinery.store.query.equality.LiteralEqualityHelper;
10import tools.refinery.store.query.literal.Reduction; 11import tools.refinery.store.query.literal.Reduction;
11import tools.refinery.store.query.term.Parameter; 12import tools.refinery.store.query.term.Parameter;
13import tools.refinery.store.query.view.AnySymbolView;
12 14
13import java.util.List; 15import java.util.List;
14 16
15public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint { 17public 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
12public enum Modality { 12public 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 */
6package tools.refinery.store.reasoning.literal; 6package tools.refinery.store.reasoning.literal;
7 7
8import tools.refinery.store.query.InvalidQueryException;
8import tools.refinery.store.query.literal.CallLiteral; 9import tools.refinery.store.query.literal.CallLiteral;
9 10
10public final class PartialLiterals { 11public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10
11public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10
11public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.tuple.Tuple;
13
14import java.util.Objects;
15
16public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple;
12
13public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10import tools.refinery.store.tuple.Tuple;
11
12public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.seed.ModelSeed;
10
11@FunctionalInterface
12public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12
13public 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 */
6package tools.refinery.store.reasoning.refinement;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.representation.Symbol;
10
11public 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Map;
13import java.util.Objects;
14
15record 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.tuple.Tuple;
12
13import java.util.Collections;
14import java.util.LinkedHashMap;
15import java.util.Map;
16import java.util.Set;
17import java.util.function.Consumer;
18
19public 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 @@
6package tools.refinery.store.reasoning.seed; 6package tools.refinery.store.reasoning.seed;
7 7
8import tools.refinery.store.map.Cursor; 8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.reasoning.representation.PartialSymbol;
10import tools.refinery.store.representation.Symbol;
9import tools.refinery.store.tuple.Tuple; 11import tools.refinery.store.tuple.Tuple;
10 12
13import java.util.Collections;
14import java.util.LinkedHashMap;
15import java.util.Map;
16
11public interface Seed<T> { 17public 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
10import tools.refinery.store.reasoning.representation.PartialSymbol;
11import tools.refinery.store.representation.Symbol;
12
13public 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 */
6package tools.refinery.store.reasoning.seed;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.tuple.Tuple;
10
11public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.query.substitution.Substitution;
9import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.query.literal.Literal;
13
14import java.util.*;
15
16public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.representation.TruthValue;
9
10public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
11
12public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.dse.transition.objectives.Criteria;
10import tools.refinery.store.dse.transition.objectives.Criterion;
11import tools.refinery.store.dse.transition.objectives.Objective;
12import tools.refinery.store.dse.transition.objectives.Objectives;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.query.Constraint;
15import tools.refinery.store.query.dnf.Query;
16import tools.refinery.store.query.dnf.QueryBuilder;
17import tools.refinery.store.query.dnf.RelationalQuery;
18import tools.refinery.store.query.literal.Literal;
19import tools.refinery.store.query.term.NodeVariable;
20import tools.refinery.store.query.view.MayView;
21import tools.refinery.store.query.view.MustView;
22import tools.refinery.store.reasoning.ReasoningAdapter;
23import tools.refinery.store.reasoning.ReasoningBuilder;
24import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
25import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
26import tools.refinery.store.reasoning.interpretation.QueryBasedRelationInterpretationFactory;
27import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter;
28import tools.refinery.store.reasoning.lifting.DnfLifter;
29import tools.refinery.store.reasoning.literal.Concreteness;
30import tools.refinery.store.reasoning.literal.Modality;
31import tools.refinery.store.reasoning.literal.PartialLiterals;
32import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
33import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
34import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
35import tools.refinery.store.reasoning.refinement.StorageRefiner;
36import tools.refinery.store.reasoning.representation.PartialRelation;
37import tools.refinery.store.representation.AnySymbol;
38import tools.refinery.store.representation.Symbol;
39import tools.refinery.store.representation.TruthValue;
40
41import java.util.ArrayList;
42import java.util.function.BiConsumer;
43
44import static tools.refinery.store.query.literal.Literals.not;
45
46@SuppressWarnings("UnusedReturnValue")
47public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import org.jetbrains.annotations.Nullable;
9import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
10import tools.refinery.store.dse.transition.Rule;
11import tools.refinery.store.dse.transition.objectives.Criterion;
12import tools.refinery.store.dse.transition.objectives.Objective;
13import tools.refinery.store.model.ModelStoreBuilder;
14import tools.refinery.store.reasoning.ReasoningBuilder;
15import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
16import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
17import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
18import tools.refinery.store.reasoning.refinement.StorageRefiner;
19import tools.refinery.store.reasoning.representation.PartialSymbol;
20import tools.refinery.store.reasoning.seed.SeedInitializer;
21import tools.refinery.store.representation.AnySymbol;
22import tools.refinery.store.representation.Symbol;
23
24import java.util.ArrayList;
25import java.util.List;
26
27@SuppressWarnings("UnusedReturnValue")
28public 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 */
6package tools.refinery.store.reasoning.translator;
7
8public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.literal.Modality;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.List;
18
19public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
9
10public 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 */
6package tools.refinery.store.reasoning.translator;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.ReasoningBuilder;
10
11import java.util.Collection;
12
13public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.model.Interpretation;
10import tools.refinery.store.query.resultset.ResultSet;
11import tools.refinery.store.reasoning.MergeResult;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningAdapter;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.seed.Seed;
11import tools.refinery.store.reasoning.seed.UniformSeed;
12import tools.refinery.store.reasoning.translator.TranslatedRelation;
13import tools.refinery.store.reasoning.translator.TranslationUnit;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16
17import java.util.Collection;
18import java.util.List;
19
20public 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 */
6package tools.refinery.store.reasoning.translator.base;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.literal.CallPolarity;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.reasoning.PartialInterpretation;
13import tools.refinery.store.reasoning.ReasoningBuilder;
14import tools.refinery.store.reasoning.literal.Modality;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.translator.Advice;
17import tools.refinery.store.reasoning.translator.TranslatedRelation;
18import tools.refinery.store.representation.Symbol;
19import tools.refinery.store.representation.TruthValue;
20
21import java.util.List;
22
23class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.dse.transition.DesignSpaceExplorationBuilder;
9import tools.refinery.store.dse.transition.Rule;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.model.ModelStoreConfiguration;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.literal.Connectivity;
15import tools.refinery.store.query.literal.Literal;
16import tools.refinery.store.query.literal.RepresentativeElectionLiteral;
17import tools.refinery.store.query.term.Variable;
18import tools.refinery.store.query.view.AnySymbolView;
19import tools.refinery.store.reasoning.lifting.DnfLifter;
20import tools.refinery.store.reasoning.literal.Concreteness;
21import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
22import tools.refinery.store.reasoning.literal.Modality;
23import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
24import tools.refinery.store.reasoning.representation.PartialRelation;
25import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
26import tools.refinery.store.reasoning.translator.RoundingMode;
27import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
28import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
29import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
30import tools.refinery.store.representation.Symbol;
31import tools.refinery.store.representation.cardinality.CardinalityIntervals;
32import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
33
34import java.util.ArrayList;
35import java.util.List;
36import java.util.Map;
37
38import static tools.refinery.store.query.literal.Literals.check;
39import static tools.refinery.store.query.literal.Literals.not;
40import static tools.refinery.store.query.term.int_.IntTerms.constant;
41import static tools.refinery.store.query.term.int_.IntTerms.less;
42import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL;
43import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
44import static tools.refinery.store.reasoning.actions.PartialActionLiterals.focus;
45import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
46
47public 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
11
12public 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
12import tools.refinery.store.reasoning.representation.PartialRelation;
13import tools.refinery.store.reasoning.representation.PartialSymbol;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.ArrayList;
19import java.util.Set;
20
21class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.Objects;
12import java.util.Set;
13
14final 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.Symbol;
11
12import java.util.Objects;
13
14abstract 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.dnf.RelationalQuery;
10import tools.refinery.store.query.literal.Literal;
11import tools.refinery.store.query.term.NodeVariable;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
14import tools.refinery.store.reasoning.representation.PartialRelation;
15import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
16import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
17
18import java.util.ArrayList;
19import java.util.List;
20
21import static tools.refinery.store.query.literal.Literals.check;
22import static tools.refinery.store.query.term.int_.IntTerms.constant;
23import static tools.refinery.store.query.term.int_.IntTerms.less;
24import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
25
26class 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.tuple.Tuple;
16
17class 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.dnf.RelationalQuery;
13import tools.refinery.store.query.view.ForbiddenView;
14import tools.refinery.store.reasoning.lifting.DnfLifter;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
21import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24
25import static tools.refinery.store.query.literal.Literals.not;
26import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
27import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
28import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
29
30public 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.ConcreteSymbolRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialRelation;
12import tools.refinery.store.reasoning.representation.PartialSymbol;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15import tools.refinery.store.tuple.Tuple;
16
17class 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 */
6package tools.refinery.store.reasoning.translator.crossreference;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.view.ForbiddenView;
13import tools.refinery.store.reasoning.lifting.DnfLifter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.Modality;
16import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator;
20import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue;
22
23import static tools.refinery.store.query.literal.Literals.not;
24import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.*;
26import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.MULTI_VIEW;
27
28public 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyBuilder;
12
13import java.util.Collection;
14
15public 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.containment.ContainmentInfo;
10import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo;
11import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo;
12import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy;
13
14import java.util.Map;
15
16public 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.containment.ContainmentInfo;
12import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceInfo;
13import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceInfo;
14import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
15import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
16import tools.refinery.store.reasoning.translator.typehierarchy.TypeInfo;
17
18import java.util.*;
19
20public 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
11import tools.refinery.store.reasoning.translator.crossreference.DirectedCrossReferenceTranslator;
12import tools.refinery.store.reasoning.translator.crossreference.UndirectedCrossReferenceTranslator;
13import tools.refinery.store.reasoning.translator.opposite.OppositeRelationTranslator;
14import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
15
16public 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity;
10
11public 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.representation.cardinality.CardinalityInterval;
15import tools.refinery.store.representation.cardinality.CardinalityIntervals;
16import tools.refinery.store.tuple.Tuple;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.dnf.RelationalQuery;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.CallLiteral;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.view.AnySymbolView;
15import tools.refinery.store.reasoning.interpretation.QueryBasedRelationRewriter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.literal.Modality;
18import tools.refinery.store.representation.cardinality.UpperCardinalities;
19import tools.refinery.store.representation.cardinality.UpperCardinality;
20
21import java.util.List;
22import java.util.Set;
23
24import static tools.refinery.store.query.literal.Literals.check;
25import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant;
26import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.lessEq;
27
28class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.representation.cardinality.CardinalityInterval;
15import tools.refinery.store.representation.cardinality.CardinalityIntervals;
16import tools.refinery.store.tuple.Tuple;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.view.AbstractFunctionView;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.representation.cardinality.CardinalityInterval;
12
13class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import org.jetbrains.annotations.NotNull;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.ReasoningAdapter;
11import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
12import tools.refinery.store.reasoning.seed.ModelSeed;
13import tools.refinery.store.reasoning.translator.TranslationException;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.representation.cardinality.CardinalityInterval;
17import tools.refinery.store.representation.cardinality.CardinalityIntervals;
18import tools.refinery.store.tuple.Tuple;
19
20import java.util.Arrays;
21import java.util.HashMap;
22import java.util.function.Function;
23
24class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.reasoning.refinement.StorageRefiner;
11import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.cardinality.CardinalityInterval;
13import tools.refinery.store.representation.cardinality.CardinalityIntervals;
14import tools.refinery.store.tuple.Tuple;
15
16class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.dse.propagation.PropagationBuilder;
9import tools.refinery.store.dse.transition.Rule;
10import tools.refinery.store.dse.transition.objectives.Criteria;
11import tools.refinery.store.dse.transition.objectives.Objectives;
12import tools.refinery.store.model.ModelStoreBuilder;
13import tools.refinery.store.model.ModelStoreConfiguration;
14import tools.refinery.store.query.dnf.Query;
15import tools.refinery.store.query.literal.Literals;
16import tools.refinery.store.query.term.Variable;
17import tools.refinery.store.query.term.int_.IntTerms;
18import tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms;
19import tools.refinery.store.query.view.AnySymbolView;
20import tools.refinery.store.reasoning.ReasoningAdapter;
21import tools.refinery.store.reasoning.ReasoningBuilder;
22import tools.refinery.store.reasoning.actions.PartialActionLiterals;
23import tools.refinery.store.reasoning.representation.PartialFunction;
24import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
25import tools.refinery.store.reasoning.translator.RoundingMode;
26import tools.refinery.store.representation.Symbol;
27import tools.refinery.store.representation.cardinality.CardinalityDomain;
28import tools.refinery.store.representation.cardinality.CardinalityInterval;
29import tools.refinery.store.representation.cardinality.UpperCardinalities;
30import tools.refinery.store.representation.cardinality.UpperCardinality;
31
32import java.util.List;
33
34import static tools.refinery.store.query.literal.Literals.check;
35import static tools.refinery.store.query.term.int_.IntTerms.*;
36
37public 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.view.TuplePreservingView;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12import tools.refinery.store.tuple.Tuple;
13
14class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import tools.refinery.store.query.term.Parameter;
9import tools.refinery.store.query.view.AbstractFunctionView;
10import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.representation.cardinality.CardinalityInterval;
12import tools.refinery.store.representation.cardinality.UpperCardinality;
13
14class 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 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10import tools.refinery.store.representation.cardinality.CardinalityInterval;
11import tools.refinery.store.representation.cardinality.CardinalityIntervals;
12import tools.refinery.store.representation.cardinality.NonEmptyCardinalityInterval;
13
14public 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 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.dse.transition.objectives.Objectives;
9import tools.refinery.store.model.ModelStoreBuilder;
10import tools.refinery.store.model.ModelStoreConfiguration;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.term.int_.IntTerms;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.lifting.DnfLifter;
16import tools.refinery.store.reasoning.literal.*;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.TranslationException;
20import tools.refinery.store.representation.cardinality.FiniteUpperCardinality;
21import tools.refinery.store.representation.cardinality.UpperCardinalities;
22import tools.refinery.store.representation.cardinality.UpperCardinality;
23
24import java.util.List;
25
26import static tools.refinery.store.query.literal.Literals.check;
27import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
28import static tools.refinery.store.query.term.int_.IntTerms.constant;
29import static tools.refinery.store.query.term.int_.IntTerms.greater;
30import static tools.refinery.store.query.term.int_.IntTerms.sub;
31import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.constant;
32import static tools.refinery.store.query.term.uppercardinality.UpperCardinalityTerms.less;
33import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust;
34import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
35
36public 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 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.representation.cardinality.CardinalityInterval;
9
10public 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 */
6package tools.refinery.store.reasoning.translator.multiplicity;
7
8import tools.refinery.store.representation.cardinality.CardinalityInterval;
9import tools.refinery.store.representation.cardinality.CardinalityIntervals;
10
11// Singleton implementation, because there is only a single complete interval.
12@SuppressWarnings("squid:S6548")
13public 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 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8
9import tools.refinery.store.map.AnyVersionedMap;
10import tools.refinery.store.map.Cursor;
11import tools.refinery.store.reasoning.ReasoningAdapter;
12import tools.refinery.store.reasoning.interpretation.AbstractPartialInterpretation;
13import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialSymbol;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.Set;
19
20class 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 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
10import tools.refinery.store.reasoning.refinement.PartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.tuple.Tuple;
13
14public 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 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.ModalConstraint;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.refinement.RefinementBasedInitializer;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
21
22import java.util.List;
23import java.util.Set;
24
25public 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 */
6package tools.refinery.store.reasoning.translator.opposite;
7
8import tools.refinery.store.tuple.Tuple;
9import tools.refinery.store.tuple.Tuple2;
10
11final 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 */
6package tools.refinery.store.reasoning.translator.predicate;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.dnf.Query;
11import tools.refinery.store.query.dnf.RelationalQuery;
12import tools.refinery.store.query.literal.Literal;
13import tools.refinery.store.query.term.NodeVariable;
14import tools.refinery.store.query.term.Variable;
15import tools.refinery.store.query.view.ForbiddenView;
16import tools.refinery.store.query.view.MayView;
17import tools.refinery.store.query.view.MustView;
18import tools.refinery.store.reasoning.representation.PartialRelation;
19import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
20import tools.refinery.store.reasoning.translator.TranslationException;
21import tools.refinery.store.representation.Symbol;
22import tools.refinery.store.representation.TruthValue;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
26import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
27
28public 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 */
6package tools.refinery.store.reasoning.translator.proxy;
7
8import tools.refinery.store.model.ModelStoreBuilder;
9import tools.refinery.store.model.ModelStoreConfiguration;
10import tools.refinery.store.query.literal.AbstractCallLiteral;
11import tools.refinery.store.query.literal.Literal;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.reasoning.interpretation.PartialRelationRewriter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.literal.ModalConstraint;
16import tools.refinery.store.reasoning.literal.Modality;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19
20import java.util.List;
21import java.util.Set;
22
23public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9
10record 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple;
11
12import java.util.Objects;
13
14class 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;
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9 9
10import java.util.Collections; 10import java.util.Collections;
11import java.util.Objects;
11import java.util.Set; 12import java.util.Set;
12 13
13record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes, 14public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Interpretation;
9import tools.refinery.store.reasoning.ReasoningAdapter;
10import tools.refinery.store.reasoning.refinement.AbstractPartialInterpretationRefiner;
11import tools.refinery.store.reasoning.representation.PartialSymbol;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.tuple.Tuple;
15
16class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.query.view.TuplePreservingView; 8import tools.refinery.store.query.view.TuplePreservingView;
10import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.Symbol;
11 11
12import java.util.Objects; 12import java.util.Objects;
13 13
14class InferredMustTypeView extends TuplePreservingView<InferredType> { 14abstract 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.tuple.Tuple;
11
12class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.*;
12
13final 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8sealed interface TypeAnalysisResult permits EliminatedType, PreservedType { 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.representation.TruthValue;
10
11import java.util.*;
12
13public 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 @@
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import tools.refinery.store.reasoning.representation.PartialRelation; 8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
9 10
10import java.util.*; 11import java.util.*;
11 12
12class TypeAnalyzer { 13public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.reasoning.representation.PartialRelation;
9import tools.refinery.store.reasoning.translator.TranslationException;
10
11import java.util.*;
12
13@SuppressWarnings("UnusedReturnValue")
14public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.refinement.PartialModelInitializer;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.seed.ModelSeed;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.representation.TruthValue;
14import tools.refinery.store.tuple.Tuple;
15
16import java.util.Arrays;
17import java.util.HashMap;
18import java.util.function.Function;
19
20public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.model.Model;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslatedRelation;
11import tools.refinery.store.reasoning.translator.TranslationUnit;
12import tools.refinery.store.representation.Symbol;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Map;
17
18public 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import tools.refinery.store.dse.transition.Rule;
9import tools.refinery.store.dse.transition.actions.ActionLiteral;
10import tools.refinery.store.model.ModelStoreBuilder;
11import tools.refinery.store.model.ModelStoreConfiguration;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.reasoning.ReasoningBuilder;
14import tools.refinery.store.reasoning.actions.PartialActionLiterals;
15import tools.refinery.store.reasoning.literal.PartialLiterals;
16import tools.refinery.store.reasoning.representation.PartialRelation;
17import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
19import tools.refinery.store.reasoning.translator.proxy.PartialRelationTranslatorProxy;
20import tools.refinery.store.representation.Symbol;
21
22import java.util.ArrayList;
23
24import static tools.refinery.store.query.literal.Literals.not;
25import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMust;
26import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
27
28public 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
10import java.util.*; 10import java.util.*;
11 11
12public record TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) { 12public 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 */
6package tools.refinery.store.reasoning;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.Variable;
13import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
14import tools.refinery.store.query.view.ForbiddenView;
15import tools.refinery.store.reasoning.literal.Concreteness;
16import tools.refinery.store.reasoning.representation.PartialRelation;
17import tools.refinery.store.reasoning.seed.ModelSeed;
18import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
19import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
20import tools.refinery.store.representation.Symbol;
21import tools.refinery.store.representation.TruthValue;
22import tools.refinery.store.tuple.Tuple;
23
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.is;
26import static org.hamcrest.Matchers.not;
27import static org.hamcrest.Matchers.nullValue;
28import static tools.refinery.store.query.literal.Literals.not;
29import static tools.refinery.store.reasoning.ReasoningAdapter.EQUALS_SYMBOL;
30import static tools.refinery.store.reasoning.ReasoningAdapter.EXISTS_SYMBOL;
31import static tools.refinery.store.reasoning.literal.PartialLiterals.may;
32import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
33
34class 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 */
6package tools.refinery.store.reasoning.lifting;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.query.dnf.Dnf;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.term.ParameterDirection;
13import tools.refinery.store.query.view.AnySymbolView;
14import tools.refinery.store.query.view.FunctionView;
15import tools.refinery.store.query.view.MustView;
16import tools.refinery.store.reasoning.ReasoningAdapter;
17import tools.refinery.store.reasoning.literal.Concreteness;
18import tools.refinery.store.reasoning.literal.ModalConstraint;
19import tools.refinery.store.reasoning.literal.Modality;
20import tools.refinery.store.reasoning.representation.PartialRelation;
21import tools.refinery.store.reasoning.representation.PartialSymbol;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24
25import java.util.List;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static tools.refinery.store.query.literal.Literals.check;
29import static tools.refinery.store.query.literal.Literals.not;
30import static tools.refinery.store.query.term.int_.IntTerms.*;
31import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
32
33class 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 */
6package tools.refinery.store.reasoning.translator.containment;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
18import tools.refinery.store.reasoning.translator.multiplicity.UnconstrainedMultiplicity;
19import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchy;
20import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
21import tools.refinery.store.representation.TruthValue;
22import tools.refinery.store.representation.cardinality.CardinalityIntervals;
23import tools.refinery.store.tuple.Tuple;
24
25import java.util.Map;
26
27import static org.hamcrest.MatcherAssert.assertThat;
28import static org.hamcrest.Matchers.is;
29import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINED_SYMBOL;
30import static tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator.CONTAINS_SYMBOL;
31import static tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator.COUNT_SYMBOL;
32
33class 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.reasoning.translator.TranslationException;
11import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
12import tools.refinery.store.representation.cardinality.CardinalityIntervals;
13
14import static org.junit.jupiter.api.Assertions.assertThrows;
15
16class 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 */
6package tools.refinery.store.reasoning.translator.metamodel;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.Model;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.reasoning.translator.containment.ContainmentHierarchyTranslator;
18import tools.refinery.store.reasoning.translator.multiobject.MultiObjectTranslator;
19import tools.refinery.store.reasoning.translator.multiplicity.ConstrainedMultiplicity;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.representation.cardinality.CardinalityIntervals;
22import tools.refinery.store.tuple.Tuple;
23
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.is;
26
27class 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 */
6package tools.refinery.store.reasoning.translator.multiobject;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.dnf.Query;
12import tools.refinery.store.query.resultset.ResultSet;
13import tools.refinery.store.query.term.Variable;
14import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
15import tools.refinery.store.reasoning.ReasoningAdapter;
16import tools.refinery.store.reasoning.ReasoningStoreAdapter;
17import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral;
18import tools.refinery.store.reasoning.literal.CountUpperBoundLiteral;
19import tools.refinery.store.reasoning.representation.PartialRelation;
20import tools.refinery.store.reasoning.seed.ModelSeed;
21import tools.refinery.store.reasoning.translator.PartialRelationTranslator;
22import tools.refinery.store.representation.Symbol;
23import tools.refinery.store.representation.TruthValue;
24import tools.refinery.store.representation.cardinality.CardinalityIntervals;
25import tools.refinery.store.representation.cardinality.UpperCardinalities;
26import tools.refinery.store.representation.cardinality.UpperCardinality;
27import tools.refinery.store.tuple.Tuple;
28
29import java.util.List;
30
31import static org.hamcrest.MatcherAssert.assertThat;
32import static org.hamcrest.Matchers.is;
33import static tools.refinery.store.query.literal.Literals.not;
34import static tools.refinery.store.reasoning.literal.PartialLiterals.must;
35
36class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
12import tools.refinery.store.reasoning.ReasoningAdapter;
13import tools.refinery.store.reasoning.ReasoningStoreAdapter;
14import tools.refinery.store.reasoning.literal.Concreteness;
15import tools.refinery.store.reasoning.representation.PartialRelation;
16import tools.refinery.store.reasoning.seed.ModelSeed;
17import tools.refinery.store.representation.TruthValue;
18import tools.refinery.store.tuple.Tuple;
19
20import static org.hamcrest.MatcherAssert.assertThat;
21import static org.hamcrest.Matchers.is;
22
23class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy; 6package tools.refinery.store.reasoning.translator.typehierarchy;
7 7
8import org.hamcrest.Matchers;
8import org.junit.jupiter.api.BeforeEach; 9import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test; 10import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation; 11import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.representation.TruthValue; 12import tools.refinery.store.representation.TruthValue;
12 13
13import java.util.LinkedHashMap;
14import java.util.Set; 14import java.util.Set;
15 15
16import static org.hamcrest.MatcherAssert.assertThat; 16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.is; 17import static org.hamcrest.Matchers.is;
18import static org.junit.jupiter.api.Assertions.assertAll; 18import static org.junit.jupiter.api.Assertions.assertAll;
19 19
20class TypeAnalyzerExampleHierarchyTest { 20class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.Test;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.TruthValue;
11
12import java.util.LinkedHashMap;
13import java.util.Set;
14
15import static org.hamcrest.MatcherAssert.assertThat;
16import static org.hamcrest.Matchers.is;
17import static org.junit.jupiter.api.Assertions.assertAll;
18import static org.junit.jupiter.api.Assertions.assertThrows;
19
20class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.junit.jupiter.api.BeforeEach;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.model.Model;
11import tools.refinery.store.model.ModelStore;
12import tools.refinery.store.query.ModelQueryAdapter;
13import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.ReasoningStoreAdapter;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.seed.ModelSeed;
19import tools.refinery.store.representation.TruthValue;
20import tools.refinery.store.tuple.Tuple;
21
22import static org.hamcrest.MatcherAssert.assertThat;
23import static org.hamcrest.Matchers.is;
24
25class 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 */
6package tools.refinery.store.reasoning.translator.typehierarchy;
7
8import org.hamcrest.Matchers;
9import org.junit.jupiter.api.Test;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.translator.TranslationException;
12import tools.refinery.store.representation.TruthValue;
13
14import java.util.Set;
15
16import static org.hamcrest.MatcherAssert.assertThat;
17import static org.hamcrest.Matchers.hasEntry;
18import static org.junit.jupiter.api.Assertions.assertAll;
19import static org.junit.jupiter.api.Assertions.assertThrows;
20
21class 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;
11import static org.hamcrest.Matchers.*; 11import static org.hamcrest.Matchers.*;
12import static org.hamcrest.Matchers.is; 12import static org.hamcrest.Matchers.is;
13 13
14class TypeAnalyzerTester { 14class 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) {