aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store-reasoning/src/main/java
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2023-09-14 19:29:36 +0200
committerLibravatar GitHub <noreply@github.com>2023-09-14 19:29:36 +0200
commit98ed3b6db5f4e51961a161050cc31c66015116e8 (patch)
tree8bfd6d9bc8d6ed23b9eb0f889dd40b6c24fe8f92 /subprojects/store-reasoning/src/main/java
parentMerge pull request #38 from nagilooh/design-space-exploration (diff)
parentMerge remote-tracking branch 'upstream/main' into partial-interpretation (diff)
downloadrefinery-98ed3b6db5f4e51961a161050cc31c66015116e8.tar.gz
refinery-98ed3b6db5f4e51961a161050cc31c66015116e8.tar.zst
refinery-98ed3b6db5f4e51961a161050cc31c66015116e8.zip
Merge pull request #39 from kris7t/partial-interpretation
Implement partial interpretation based model generation
Diffstat (limited to 'subprojects/store-reasoning/src/main/java')
-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
115 files changed, 6037 insertions, 1012 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}