diff options
author | Kristóf Marussy <kristof@marussy.com> | 2024-06-01 18:37:20 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2024-06-01 20:17:47 +0200 |
commit | 07b4048828d9ef8126282c4626dd3f0729213d91 (patch) | |
tree | 4523d01e7802585ae3a3c7ec622d1b0a1e3dfa91 | |
parent | fix(reasoning): candidate count literal rewriting (diff) | |
download | refinery-07b4048828d9ef8126282c4626dd3f0729213d91.tar.gz refinery-07b4048828d9ef8126282c4626dd3f0729213d91.tar.zst refinery-07b4048828d9ef8126282c4626dd3f0729213d91.zip |
feat: partial references
References marked as partial are not concretized during model generation. The
should be managed by the user manually using propagation rules instead.
20 files changed, 329 insertions, 158 deletions
diff --git a/subprojects/frontend/src/language/problem.grammar b/subprojects/frontend/src/language/problem.grammar index ffae220c..a172835d 100644 --- a/subprojects/frontend/src/language/problem.grammar +++ b/subprojects/frontend/src/language/problem.grammar | |||
@@ -161,7 +161,7 @@ AssertionActionArgument { VariableName | StarArgument } | |||
161 | Constant { Real | String | StarMult | LogicValue } | 161 | Constant { Real | String | StarMult | LogicValue } |
162 | 162 | ||
163 | ReferenceKind { | 163 | ReferenceKind { |
164 | kw<"refers"> | ckw<"contains"> | kw<"container"> | 164 | kw<"refers"> | ckw<"contains"> | kw<"container"> | kw<"partial"> |
165 | } | 165 | } |
166 | 166 | ||
167 | LogicValue { | 167 | LogicValue { |
diff --git a/subprojects/frontend/src/language/problemLanguageSupport.ts b/subprojects/frontend/src/language/problemLanguageSupport.ts index ae998d20..5ca162d9 100644 --- a/subprojects/frontend/src/language/problemLanguageSupport.ts +++ b/subprojects/frontend/src/language/problemLanguageSupport.ts | |||
@@ -36,7 +36,7 @@ const parserWithMetadata = parser.configure({ | |||
36 | 'import as declare atom multi': t.definitionKeyword, | 36 | 'import as declare atom multi': t.definitionKeyword, |
37 | 'extern datatype aggregator': t.definitionKeyword, | 37 | 'extern datatype aggregator': t.definitionKeyword, |
38 | rule: t.definitionKeyword, | 38 | rule: t.definitionKeyword, |
39 | 'abstract extends refers contains container opposite': t.modifier, | 39 | 'abstract extends refers contains container partial opposite': t.modifier, |
40 | default: t.modifier, | 40 | default: t.modifier, |
41 | 'propagation decision': t.modifier, | 41 | 'propagation decision': t.modifier, |
42 | 'true false unknown error': t.keyword, | 42 | 'true false unknown error': t.keyword, |
diff --git a/subprojects/language-model/problem.aird b/subprojects/language-model/problem.aird index 8682236b..74c658d4 100644 --- a/subprojects/language-model/problem.aird +++ b/subprojects/language-model/problem.aird | |||
@@ -7,7 +7,7 @@ | |||
7 | <semanticResources>build/resources/main/model/problem.genmodel</semanticResources> | 7 | <semanticResources>build/resources/main/model/problem.genmodel</semanticResources> |
8 | <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg"> | 8 | <ownedViews xmi:type="viewpoint:DView" uid="_CsAAYKA4EeuqkpDnuik1sg"> |
9 | <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/> | 9 | <viewpoint xmi:type="description:Viewpoint" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']"/> |
10 | <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="1715963399779"> | 10 | <ownedRepresentationDescriptors xmi:type="viewpoint:DRepresentationDescriptor" uid="_CsYa4KA4EeuqkpDnuik1sg" name="declarations" repPath="#_CsUwgKA4EeuqkpDnuik1sg" changeId="1717085395802"> |
11 | <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/> | 11 | <description xmi:type="description_1:DiagramDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']"/> |
12 | <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/> | 12 | <target xmi:type="ecore:EPackage" href="src/main/resources/model/problem.ecore#/"/> |
13 | </ownedRepresentationDescriptors> | 13 | </ownedRepresentationDescriptors> |
@@ -305,11 +305,15 @@ | |||
305 | <styles xmi:type="notation:FontStyle" xmi:id="_9ZwJoc9jEe6T2u19X9cqmQ" fontName="Noto Sans" fontHeight="8"/> | 305 | <styles xmi:type="notation:FontStyle" xmi:id="_9ZwJoc9jEe6T2u19X9cqmQ" fontName="Noto Sans" fontHeight="8"/> |
306 | <layoutConstraint xmi:type="notation:Location" xmi:id="_9ZwJos9jEe6T2u19X9cqmQ"/> | 306 | <layoutConstraint xmi:type="notation:Location" xmi:id="_9ZwJos9jEe6T2u19X9cqmQ"/> |
307 | </children> | 307 | </children> |
308 | <children xmi:type="notation:Node" xmi:id="_Cl4ksB6fEe-D29eT4GV5Hg" type="3010" element="_Ck5GMB6fEe-D29eT4GV5Hg"> | ||
309 | <styles xmi:type="notation:FontStyle" xmi:id="_Cl4ksR6fEe-D29eT4GV5Hg" fontName="Noto Sans" fontHeight="8"/> | ||
310 | <layoutConstraint xmi:type="notation:Location" xmi:id="_Cl4ksh6fEe-D29eT4GV5Hg"/> | ||
311 | </children> | ||
308 | <styles xmi:type="notation:SortingStyle" xmi:id="_xp2JgjNlEe2fD4dIhR_vzA"/> | 312 | <styles xmi:type="notation:SortingStyle" xmi:id="_xp2JgjNlEe2fD4dIhR_vzA"/> |
309 | <styles xmi:type="notation:FilteringStyle" xmi:id="_xp2JgzNlEe2fD4dIhR_vzA"/> | 313 | <styles xmi:type="notation:FilteringStyle" xmi:id="_xp2JgzNlEe2fD4dIhR_vzA"/> |
310 | </children> | 314 | </children> |
311 | <styles xmi:type="notation:ShapeStyle" xmi:id="_xp1icTNlEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/> | 315 | <styles xmi:type="notation:ShapeStyle" xmi:id="_xp1icTNlEe2fD4dIhR_vzA" fontName="Noto Sans" fontHeight="8"/> |
312 | <layoutConstraint xmi:type="notation:Bounds" xmi:id="_xp1icjNlEe2fD4dIhR_vzA" x="574" y="1280" width="120" height="100"/> | 316 | <layoutConstraint xmi:type="notation:Bounds" xmi:id="_xp1icjNlEe2fD4dIhR_vzA" x="574" y="1280" width="120" height="115"/> |
313 | </children> | 317 | </children> |
314 | <children xmi:type="notation:Node" xmi:id="_782skF9mEe2rXNsIDUvqhw" type="2003" element="_78pRMF9mEe2rXNsIDUvqhw"> | 318 | <children xmi:type="notation:Node" xmi:id="_782skF9mEe2rXNsIDUvqhw" type="2003" element="_78pRMF9mEe2rXNsIDUvqhw"> |
315 | <children xmi:type="notation:Node" xmi:id="_783ToF9mEe2rXNsIDUvqhw" type="5007"/> | 319 | <children xmi:type="notation:Node" xmi:id="_783ToF9mEe2rXNsIDUvqhw" type="5007"/> |
@@ -2226,6 +2230,14 @@ | |||
2226 | </ownedStyle> | 2230 | </ownedStyle> |
2227 | <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/> | 2231 | <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/> |
2228 | </ownedElements> | 2232 | </ownedElements> |
2233 | <ownedElements xmi:type="diagram:DNodeListElement" uid="_Ck5GMB6fEe-D29eT4GV5Hg" name="PARTIAL" tooltipText=""> | ||
2234 | <target xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//ReferenceKind/PARTIAL"/> | ||
2235 | <semanticElements xmi:type="ecore:EEnumLiteral" href="src/main/resources/model/problem.ecore#//ReferenceKind/PARTIAL"/> | ||
2236 | <ownedStyle xmi:type="diagram:BundledImage" uid="_Ck67YB6fEe-D29eT4GV5Hg" labelAlignment="LEFT"> | ||
2237 | <description xmi:type="style:BundledImageDescription" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']/@style"/> | ||
2238 | </ownedStyle> | ||
2239 | <actualMapping xmi:type="description_1:NodeMapping" href="platform:/plugin/org.eclipse.emf.ecoretools.design/description/ecore.odesign#//@ownedViewpoints[name='Design']/@ownedRepresentations[name='Entities']/@defaultLayer/@containerMappings[name='EC%20EEnum']/@subNodeMappings[name='EC%20EEnumLiteral']"/> | ||
2240 | </ownedElements> | ||
2229 | </ownedDiagramElements> | 2241 | </ownedDiagramElements> |
2230 | <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_78pRMF9mEe2rXNsIDUvqhw" name="FunctionDefinition" tooltipText="" outgoingEdges="_rKoQHF9nEe2rXNsIDUvqhw _S6YCJl9wEe2rXNsIDUvqhw _sMPaBmTvEe2qdtyPWAtoxA _RjRnQs9lEe6T2u19X9cqmQ" width="12" height="10"> | 2242 | <ownedDiagramElements xmi:type="diagram:DNodeList" uid="_78pRMF9mEe2rXNsIDUvqhw" name="FunctionDefinition" tooltipText="" outgoingEdges="_rKoQHF9nEe2rXNsIDUvqhw _S6YCJl9wEe2rXNsIDUvqhw _sMPaBmTvEe2qdtyPWAtoxA _RjRnQs9lEe6T2u19X9cqmQ" width="12" height="10"> |
2231 | <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/> | 2243 | <target xmi:type="ecore:EClass" href="src/main/resources/model/problem.ecore#//FunctionDefinition"/> |
diff --git a/subprojects/language-model/src/main/resources/model/problem.ecore b/subprojects/language-model/src/main/resources/model/problem.ecore index ed56d3b1..e830c724 100644 --- a/subprojects/language-model/src/main/resources/model/problem.ecore +++ b/subprojects/language-model/src/main/resources/model/problem.ecore | |||
@@ -153,6 +153,7 @@ | |||
153 | <eLiterals name="REFERENCE" value="2"/> | 153 | <eLiterals name="REFERENCE" value="2"/> |
154 | <eLiterals name="CONTAINMENT" value="3"/> | 154 | <eLiterals name="CONTAINMENT" value="3"/> |
155 | <eLiterals name="CONTAINER" value="3"/> | 155 | <eLiterals name="CONTAINER" value="3"/> |
156 | <eLiterals name="PARTIAL" value="4"/> | ||
156 | </eClassifiers> | 157 | </eClassifiers> |
157 | <eClassifiers xsi:type="ecore:EClass" name="Expr" abstract="true"/> | 158 | <eClassifiers xsi:type="ecore:EClass" name="Expr" abstract="true"/> |
158 | <eClassifiers xsi:type="ecore:EClass" name="VariableOrNodeExpr" eSuperTypes="#//Expr"> | 159 | <eClassifiers xsi:type="ecore:EClass" name="VariableOrNodeExpr" eSuperTypes="#//Expr"> |
diff --git a/subprojects/language-model/src/main/resources/model/problem.genmodel b/subprojects/language-model/src/main/resources/model/problem.genmodel index c7044885..2ceb74a6 100644 --- a/subprojects/language-model/src/main/resources/model/problem.genmodel +++ b/subprojects/language-model/src/main/resources/model/problem.genmodel | |||
@@ -32,6 +32,7 @@ | |||
32 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/REFERENCE"/> | 32 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/REFERENCE"/> |
33 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINMENT"/> | 33 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINMENT"/> |
34 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINER"/> | 34 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/CONTAINER"/> |
35 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//ReferenceKind/PARTIAL"/> | ||
35 | </genEnums> | 36 | </genEnums> |
36 | <genEnums typeSafeEnumCompatible="false" ecoreEnum="problem.ecore#//UnaryOp"> | 37 | <genEnums typeSafeEnumCompatible="false" ecoreEnum="problem.ecore#//UnaryOp"> |
37 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/PLUS"/> | 38 | <genEnumLiterals ecoreEnumLiteral="problem.ecore#//UnaryOp/PLUS"/> |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java index 6c637e19..bd6a8ec9 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/ModelInitializer.java | |||
@@ -347,6 +347,7 @@ public class ModelInitializer { | |||
347 | var target = getPartialRelation(referenceDeclaration.getReferenceType()); | 347 | var target = getPartialRelation(referenceDeclaration.getReferenceType()); |
348 | targetTypes.add(target); | 348 | targetTypes.add(target); |
349 | boolean containment = referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT; | 349 | boolean containment = referenceDeclaration.getKind() == ReferenceKind.CONTAINMENT; |
350 | boolean partial = referenceDeclaration.getKind() == ReferenceKind.PARTIAL; | ||
350 | var opposite = referenceDeclaration.getOpposite(); | 351 | var opposite = referenceDeclaration.getOpposite(); |
351 | PartialRelation oppositeRelation = null; | 352 | PartialRelation oppositeRelation = null; |
352 | if (opposite != null) { | 353 | if (opposite != null) { |
@@ -367,6 +368,7 @@ public class ModelInitializer { | |||
367 | .target(target) | 368 | .target(target) |
368 | .opposite(oppositeRelation) | 369 | .opposite(oppositeRelation) |
369 | .defaultValue(defaultValue) | 370 | .defaultValue(defaultValue) |
371 | .partial(partial) | ||
370 | .build()); | 372 | .build()); |
371 | } catch (RuntimeException e) { | 373 | } catch (RuntimeException e) { |
372 | throw TracedException.addTrace(classDeclaration, e); | 374 | throw TracedException.addTrace(classDeclaration, e); |
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java index f097143f..ed4841c4 100644 --- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java +++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java | |||
@@ -310,7 +310,7 @@ public class SolutionSerializer { | |||
310 | var relation = findPartialRelation(partialRelation); | 310 | var relation = findPartialRelation(partialRelation); |
311 | var cursor = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, partialRelation).getAll(); | 311 | var cursor = reasoningAdapter.getPartialInterpretation(Concreteness.CANDIDATE, partialRelation).getAll(); |
312 | // Make sure to output assertions in a deterministic order. | 312 | // Make sure to output assertions in a deterministic order. |
313 | var sortedTuples = new TreeSet<Tuple>(); | 313 | var sortedTuples = new TreeMap<Tuple, LogicValue>(); |
314 | while (cursor.move()) { | 314 | while (cursor.move()) { |
315 | var tuple = cursor.getKey(); | 315 | var tuple = cursor.getKey(); |
316 | var from = nodes.get(tuple.get(0)); | 316 | var from = nodes.get(tuple.get(0)); |
@@ -320,17 +320,20 @@ public class SolutionSerializer { | |||
320 | continue; | 320 | continue; |
321 | } | 321 | } |
322 | var value = cursor.getValue(); | 322 | var value = cursor.getValue(); |
323 | if (!value.isConcrete()) { | 323 | var logicValue = switch (value) { |
324 | throw new IllegalStateException("Invalid %s %s for tuple %s".formatted(partialRelation, value, tuple)); | 324 | case TRUE -> LogicValue.TRUE; |
325 | } | 325 | case FALSE -> throw new IllegalStateException("Invalid %s %s for tuple %s" |
326 | if (value.may()) { | 326 | .formatted(partialRelation, value, tuple)); |
327 | sortedTuples.add(tuple); | 327 | case UNKNOWN -> LogicValue.UNKNOWN; |
328 | } | 328 | case ERROR -> LogicValue.ERROR; |
329 | }; | ||
330 | sortedTuples.put(tuple, logicValue); | ||
329 | } | 331 | } |
330 | for (var tuple : sortedTuples) { | 332 | for (var entry : sortedTuples.entrySet()) { |
333 | var tuple = entry.getKey(); | ||
331 | var from = nodes.get(tuple.get(0)); | 334 | var from = nodes.get(tuple.get(0)); |
332 | var to = nodes.get(tuple.get(1)); | 335 | var to = nodes.get(tuple.get(1)); |
333 | addAssertion(relation, LogicValue.TRUE, from, to); | 336 | addAssertion(relation, entry.getValue(), from, to); |
334 | } | 337 | } |
335 | } | 338 | } |
336 | 339 | ||
diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext index ebb5bf71..10e994a0 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext | |||
@@ -45,7 +45,7 @@ AggregatorDeclaration: | |||
45 | "extern" "aggregator" name=Identifier "."; | 45 | "extern" "aggregator" name=Identifier "."; |
46 | 46 | ||
47 | enum ReferenceKind: | 47 | enum ReferenceKind: |
48 | REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container"; | 48 | REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container" | PARTIAL="partial"; |
49 | 49 | ||
50 | ReferenceDeclaration: | 50 | ReferenceDeclaration: |
51 | (referenceType=[Relation|NonContainmentQualifiedName] | | 51 | (referenceType=[Relation|NonContainmentQualifiedName] | |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java index 9daa8f61..e1820261 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java | |||
@@ -42,13 +42,11 @@ public final class ProblemUtil { | |||
42 | } | 42 | } |
43 | 43 | ||
44 | public static boolean isImplicit(EObject eObject) { | 44 | public static boolean isImplicit(EObject eObject) { |
45 | if (eObject instanceof Node node) { | 45 | return switch (eObject) { |
46 | return isImplicitNode(node); | 46 | case Node node -> isImplicitNode(node); |
47 | } else if (eObject instanceof Variable variable) { | 47 | case Variable variable -> isImplicitVariable(variable); |
48 | return isImplicitVariable(variable); | 48 | default -> false; |
49 | } else { | 49 | }; |
50 | return false; | ||
51 | } | ||
52 | } | 50 | } |
53 | 51 | ||
54 | public static boolean isError(EObject eObject) { | 52 | public static boolean isError(EObject eObject) { |
@@ -119,7 +117,7 @@ public final class ProblemUtil { | |||
119 | return false; | 117 | return false; |
120 | } | 118 | } |
121 | return switch (kind) { | 119 | return switch (kind) { |
122 | case CONTAINMENT -> false; | 120 | case CONTAINMENT, PARTIAL -> false; |
123 | case CONTAINER -> true; | 121 | case CONTAINER -> true; |
124 | case DEFAULT, REFERENCE -> { | 122 | case DEFAULT, REFERENCE -> { |
125 | var opposite = referenceDeclaration.getOpposite(); | 123 | var opposite = referenceDeclaration.getOpposite(); |
diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index 9f5fdeae..754572d1 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java | |||
@@ -318,6 +318,11 @@ public class ProblemValidator extends AbstractProblemValidator { | |||
318 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, | 318 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, |
319 | INVALID_OPPOSITE_ISSUE); | 319 | INVALID_OPPOSITE_ISSUE); |
320 | } | 320 | } |
321 | } else if (kind == ReferenceKind.PARTIAL && opposite != null && opposite.getKind() != ReferenceKind.PARTIAL) { | ||
322 | acceptError("Opposite '%s' of partial reference '%s' is not a partial reference." | ||
323 | .formatted(opposite.getName(), referenceDeclaration.getName()), | ||
324 | referenceDeclaration, ProblemPackage.Literals.REFERENCE_DECLARATION__OPPOSITE, 0, | ||
325 | INVALID_OPPOSITE_ISSUE); | ||
321 | } | 326 | } |
322 | } | 327 | } |
323 | 328 | ||
diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java index 57602377..d73ef8e7 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/validation/OppositeValidationTest.java | |||
@@ -57,6 +57,18 @@ class OppositeValidationTest { | |||
57 | class Foo { | 57 | class Foo { |
58 | Foo foo[] opposite foo | 58 | Foo foo[] opposite foo |
59 | } | 59 | } |
60 | """, """ | ||
61 | class Foo { | ||
62 | partial Bar bar opposite foo | ||
63 | } | ||
64 | |||
65 | class Bar { | ||
66 | partial Foo foo opposite bar | ||
67 | } | ||
68 | """, """ | ||
69 | class Foo { | ||
70 | partial Foo foo[] opposite foo | ||
71 | } | ||
60 | """}) | 72 | """}) |
61 | void validOppositeTest(String text) { | 73 | void validOppositeTest(String text) { |
62 | var problem = parseHelper.parse(text); | 74 | var problem = parseHelper.parse(text); |
@@ -188,7 +200,7 @@ class OppositeValidationTest { | |||
188 | } | 200 | } |
189 | 201 | ||
190 | @ParameterizedTest | 202 | @ParameterizedTest |
191 | @ValueSource(strings = {"Foo foo", "container Foo foo"}) | 203 | @ValueSource(strings = {"Foo foo", "container Foo foo", "partial Foo foo"}) |
192 | void containerInvalidOppositeTest(String reference) { | 204 | void containerInvalidOppositeTest(String reference) { |
193 | var problem = parseHelper.parse(""" | 205 | var problem = parseHelper.parse(""" |
194 | class Foo { | 206 | class Foo { |
@@ -203,7 +215,27 @@ class OppositeValidationTest { | |||
203 | assertThat(issues, hasItem(allOf( | 215 | assertThat(issues, hasItem(allOf( |
204 | hasProperty("severity", is(Diagnostic.ERROR)), | 216 | hasProperty("severity", is(Diagnostic.ERROR)), |
205 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), | 217 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), |
206 | hasProperty("message", stringContainsInOrder("foo", "bar")) | 218 | hasProperty("message", stringContainsInOrder("foo", "container", "bar")) |
219 | ))); | ||
220 | } | ||
221 | |||
222 | @ParameterizedTest | ||
223 | @ValueSource(strings = {"Foo foo", "contains Foo foo", "container Foo foo"}) | ||
224 | void partialWithConcreteOppositeTest(String reference) { | ||
225 | var problem = parseHelper.parse(""" | ||
226 | class Foo { | ||
227 | partial Bar bar opposite foo | ||
228 | } | ||
229 | |||
230 | class Bar { | ||
231 | %s opposite bar | ||
232 | } | ||
233 | """.formatted(reference)); | ||
234 | var issues = problem.validate(); | ||
235 | assertThat(issues, hasItem(allOf( | ||
236 | hasProperty("severity", is(Diagnostic.ERROR)), | ||
237 | hasProperty("issueCode", is(ProblemValidator.INVALID_OPPOSITE_ISSUE)), | ||
238 | hasProperty("message", stringContainsInOrder("foo", "partial", "bar")) | ||
207 | ))); | 239 | ))); |
208 | } | 240 | } |
209 | } | 241 | } |
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 index 26449df5..56c9d682 100644 --- 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 | |||
@@ -1,16 +1,18 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.crossreference; | 6 | package tools.refinery.store.reasoning.translator.crossreference; |
7 | 7 | ||
8 | import tools.refinery.logic.dnf.Query; | 8 | import tools.refinery.logic.dnf.Query; |
9 | import tools.refinery.logic.dnf.QueryBuilder; | ||
9 | import tools.refinery.logic.dnf.RelationalQuery; | 10 | import tools.refinery.logic.dnf.RelationalQuery; |
10 | import tools.refinery.logic.literal.Literal; | 11 | import tools.refinery.logic.literal.Literal; |
11 | import tools.refinery.logic.term.NodeVariable; | 12 | import tools.refinery.logic.term.NodeVariable; |
12 | import tools.refinery.logic.term.Variable; | 13 | import tools.refinery.logic.term.Variable; |
13 | import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality; | 14 | import tools.refinery.logic.term.uppercardinality.FiniteUpperCardinality; |
15 | import tools.refinery.store.reasoning.literal.CountCandidateLowerBoundLiteral; | ||
14 | import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral; | 16 | import tools.refinery.store.reasoning.literal.CountLowerBoundLiteral; |
15 | import tools.refinery.store.reasoning.representation.PartialRelation; | 17 | import tools.refinery.store.reasoning.representation.PartialRelation; |
16 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 18 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
@@ -21,6 +23,7 @@ import java.util.List; | |||
21 | import static tools.refinery.logic.literal.Literals.check; | 23 | import static tools.refinery.logic.literal.Literals.check; |
22 | import static tools.refinery.logic.term.int_.IntTerms.constant; | 24 | import static tools.refinery.logic.term.int_.IntTerms.constant; |
23 | import static tools.refinery.logic.term.int_.IntTerms.less; | 25 | import static tools.refinery.logic.term.int_.IntTerms.less; |
26 | import static tools.refinery.store.reasoning.literal.PartialLiterals.candidateMay; | ||
24 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; | 27 | import static tools.refinery.store.reasoning.literal.PartialLiterals.may; |
25 | 28 | ||
26 | class CrossReferenceUtils { | 29 | class CrossReferenceUtils { |
@@ -30,6 +33,34 @@ class CrossReferenceUtils { | |||
30 | 33 | ||
31 | public static RelationalQuery createMayHelper(PartialRelation linkType, PartialRelation type, | 34 | public static RelationalQuery createMayHelper(PartialRelation linkType, PartialRelation type, |
32 | Multiplicity multiplicity, boolean inverse) { | 35 | Multiplicity multiplicity, boolean inverse) { |
36 | var preparedBuilder = prepareBuilder(linkType, inverse); | ||
37 | var literals = new ArrayList<Literal>(); | ||
38 | literals.add(may(type.call(preparedBuilder.variable()))); | ||
39 | if (multiplicity.multiplicity().upperBound() instanceof FiniteUpperCardinality(var finiteUpperBound)) { | ||
40 | var existingLinks = Variable.of("existingLinks", Integer.class); | ||
41 | literals.add(new CountLowerBoundLiteral(existingLinks, linkType, preparedBuilder.arguments())); | ||
42 | literals.add(check(less(existingLinks, constant(finiteUpperBound)))); | ||
43 | } | ||
44 | return preparedBuilder.builder().clause(literals).build(); | ||
45 | } | ||
46 | |||
47 | public static RelationalQuery createCandidateMayHelper(PartialRelation linkType, PartialRelation type, | ||
48 | Multiplicity multiplicity, boolean inverse) { | ||
49 | var preparedBuilder = prepareBuilder(linkType, inverse); | ||
50 | var literals = new ArrayList<Literal>(); | ||
51 | literals.add(candidateMay(type.call(preparedBuilder.variable()))); | ||
52 | if (multiplicity.multiplicity().upperBound() instanceof FiniteUpperCardinality(var finiteUpperBound)) { | ||
53 | var existingLinks = Variable.of("existingLinks", Integer.class); | ||
54 | literals.add(new CountCandidateLowerBoundLiteral(existingLinks, linkType, preparedBuilder.arguments())); | ||
55 | literals.add(check(less(existingLinks, constant(finiteUpperBound)))); | ||
56 | } | ||
57 | return preparedBuilder.builder().clause(literals).build(); | ||
58 | } | ||
59 | |||
60 | private record PreparedBuilder(QueryBuilder builder, NodeVariable variable, List<Variable> arguments) { | ||
61 | } | ||
62 | |||
63 | private static PreparedBuilder prepareBuilder(PartialRelation linkType, boolean inverse) { | ||
33 | String name; | 64 | String name; |
34 | NodeVariable variable; | 65 | NodeVariable variable; |
35 | List<Variable> arguments; | 66 | List<Variable> arguments; |
@@ -44,14 +75,6 @@ class CrossReferenceUtils { | |||
44 | } | 75 | } |
45 | var builder = Query.builder(linkType.name() + "#mayNew" + name); | 76 | var builder = Query.builder(linkType.name() + "#mayNew" + name); |
46 | builder.parameter(variable); | 77 | builder.parameter(variable); |
47 | var literals = new ArrayList<Literal>(); | 78 | return new PreparedBuilder(builder, variable, arguments); |
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 | } | 79 | } |
57 | } | 80 | } |
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 index 982f835f..ef5bcebb 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -11,7 +11,7 @@ import tools.refinery.logic.term.truthvalue.TruthValue; | |||
11 | 11 | ||
12 | public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, | 12 | public record DirectedCrossReferenceInfo(PartialRelation sourceType, Multiplicity sourceMultiplicity, |
13 | PartialRelation targetType, Multiplicity targetMultiplicity, | 13 | PartialRelation targetType, Multiplicity targetMultiplicity, |
14 | TruthValue defaultValue) { | 14 | TruthValue defaultValue, boolean partial) { |
15 | public boolean isConstrained() { | 15 | public boolean isConstrained() { |
16 | return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); | 16 | return sourceMultiplicity.isConstrained() || targetMultiplicity.isConstrained(); |
17 | } | 17 | } |
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 index 5bf1f5ab..1985a43f 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -18,6 +18,7 @@ import tools.refinery.store.reasoning.literal.Concreteness; | |||
18 | import tools.refinery.store.reasoning.literal.Modality; | 18 | import tools.refinery.store.reasoning.literal.Modality; |
19 | import tools.refinery.store.reasoning.representation.PartialRelation; | 19 | import tools.refinery.store.reasoning.representation.PartialRelation; |
20 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 20 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
21 | import tools.refinery.store.reasoning.translator.RoundingMode; | ||
21 | import tools.refinery.store.reasoning.translator.TranslationException; | 22 | import tools.refinery.store.reasoning.translator.TranslationException; |
22 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | 23 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; |
23 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 24 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
@@ -42,27 +43,77 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration | |||
42 | 43 | ||
43 | @Override | 44 | @Override |
44 | public void apply(ModelStoreBuilder storeBuilder) { | 45 | public void apply(ModelStoreBuilder storeBuilder) { |
45 | var name = linkType.name(); | ||
46 | var sourceType = info.sourceType(); | 46 | var sourceType = info.sourceType(); |
47 | var targetType = info.targetType(); | 47 | var targetType = info.targetType(); |
48 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); | ||
49 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); | ||
50 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
51 | |||
52 | var defaultValue = info.defaultValue(); | 48 | var defaultValue = info.defaultValue(); |
53 | if (defaultValue.must()) { | 49 | if (defaultValue.must()) { |
54 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" | 50 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" |
55 | .formatted(defaultValue, linkType)); | 51 | .formatted(defaultValue, linkType)); |
56 | } | 52 | } |
57 | |||
58 | var translator = PartialRelationTranslator.of(linkType); | 53 | var translator = PartialRelationTranslator.of(linkType); |
59 | translator.symbol(symbol); | 54 | translator.symbol(symbol); |
60 | if (defaultValue.may()) { | 55 | if (defaultValue.may()) { |
61 | var forbiddenView = new ForbiddenView(symbol); | 56 | configureWithDefaultUnknown(translator); |
62 | translator.may(Query.of(mayName, (builder, source, target) -> { | 57 | } else { |
58 | configureWithDefaultFalse(storeBuilder); | ||
59 | } | ||
60 | translator.refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)); | ||
61 | translator.initializer(new DirectedCrossReferenceInitializer(linkType, sourceType, targetType, symbol)); | ||
62 | if (info.partial()) { | ||
63 | translator.roundingMode(RoundingMode.NONE); | ||
64 | } else { | ||
65 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
66 | .clause( | ||
67 | may(linkType.call(source, target)), | ||
68 | not(candidateMust(linkType.call(source, target))), | ||
69 | not(MULTI_VIEW.call(source)), | ||
70 | not(MULTI_VIEW.call(target)) | ||
71 | ) | ||
72 | .action( | ||
73 | add(linkType, source, target) | ||
74 | ))); | ||
75 | } | ||
76 | storeBuilder.with(translator); | ||
77 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, | ||
78 | info.sourceMultiplicity())); | ||
79 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(targetType, linkType, true, | ||
80 | info.targetMultiplicity())); | ||
81 | } | ||
82 | |||
83 | private void configureWithDefaultUnknown(PartialRelationTranslator translator) { | ||
84 | var name = linkType.name(); | ||
85 | var sourceType = info.sourceType(); | ||
86 | var targetType = info.targetType(); | ||
87 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); | ||
88 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); | ||
89 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
90 | var forbiddenView = new ForbiddenView(symbol); | ||
91 | translator.may(Query.of(mayName, (builder, source, target) -> { | ||
92 | builder.clause( | ||
93 | mayNewSource.call(source), | ||
94 | mayNewTarget.call(target), | ||
95 | not(forbiddenView.call(source, target)) | ||
96 | ); | ||
97 | if (info.isConstrained()) { | ||
98 | // Violation of monotonicity: | ||
99 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
100 | // corresponding error pattern will already mark the node as invalid. | ||
101 | builder.clause( | ||
102 | must(linkType.call(source, target)), | ||
103 | not(forbiddenView.call(source, target)), | ||
104 | may(sourceType.call(source)), | ||
105 | may(targetType.call(target)) | ||
106 | ); | ||
107 | } | ||
108 | })); | ||
109 | if (info.partial()) { | ||
110 | var candidateMayNewSource = createCandidateMayHelper(sourceType, info.sourceMultiplicity(), false); | ||
111 | var candidateMayNewTarget = createCandidateMayHelper(targetType, info.targetMultiplicity(), true); | ||
112 | var candidateMayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.CANDIDATE); | ||
113 | translator.candidateMay(Query.of(candidateMayName, (builder, source, target) -> { | ||
63 | builder.clause( | 114 | builder.clause( |
64 | mayNewSource.call(source), | 115 | candidateMayNewSource.call(source), |
65 | mayNewTarget.call(target), | 116 | candidateMayNewTarget.call(target), |
66 | not(forbiddenView.call(source, target)) | 117 | not(forbiddenView.call(source, target)) |
67 | ); | 118 | ); |
68 | if (info.isConstrained()) { | 119 | if (info.isConstrained()) { |
@@ -70,63 +121,56 @@ public class DirectedCrossReferenceTranslator implements ModelStoreConfiguration | |||
70 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | 121 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the |
71 | // corresponding error pattern will already mark the node as invalid. | 122 | // corresponding error pattern will already mark the node as invalid. |
72 | builder.clause( | 123 | builder.clause( |
73 | must(linkType.call(source, target)), | 124 | candidateMust(linkType.call(source, target)), |
74 | not(forbiddenView.call(source, target)), | 125 | not(forbiddenView.call(source, target)), |
75 | may(sourceType.call(source)), | 126 | candidateMay(sourceType.call(source)), |
76 | may(targetType.call(target)) | 127 | candidateMay(targetType.call(target)) |
77 | ); | 128 | ); |
78 | } | 129 | } |
79 | })); | 130 | })); |
80 | } else { | 131 | } |
81 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | 132 | } |
82 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | 133 | |
134 | private RelationalQuery createMayHelper(PartialRelation type, Multiplicity multiplicity, boolean inverse) { | ||
135 | return CrossReferenceUtils.createMayHelper(linkType, type, multiplicity, inverse); | ||
136 | } | ||
137 | |||
138 | private RelationalQuery createCandidateMayHelper(PartialRelation type, Multiplicity multiplicity, boolean inverse) { | ||
139 | return CrossReferenceUtils.createCandidateMayHelper(linkType, type, multiplicity, inverse); | ||
140 | } | ||
141 | |||
142 | private void configureWithDefaultFalse(ModelStoreBuilder storeBuilder) { | ||
143 | var name = linkType.name(); | ||
144 | var sourceType = info.sourceType(); | ||
145 | var targetType = info.targetType(); | ||
146 | var mayNewSource = createMayHelper(sourceType, info.sourceMultiplicity(), false); | ||
147 | var mayNewTarget = createMayHelper(targetType, info.targetMultiplicity(), true); | ||
148 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | ||
149 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | ||
150 | builder.clause( | ||
151 | may(linkType.call(p1, p2)), | ||
152 | not(may(sourceType.call(p1))) | ||
153 | ); | ||
154 | builder.clause( | ||
155 | may(linkType.call(p1, p2)), | ||
156 | not(may(targetType.call(p2))) | ||
157 | ); | ||
158 | if (info.isConstrained()) { | ||
83 | builder.clause( | 159 | builder.clause( |
84 | may(linkType.call(p1, p2)), | 160 | may(linkType.call(p1, p2)), |
85 | not(may(sourceType.call(p1))) | 161 | not(must(linkType.call(p1, p2))), |
162 | not(mayNewSource.call(p1)) | ||
86 | ); | 163 | ); |
87 | builder.clause( | 164 | builder.clause( |
88 | may(linkType.call(p1, p2)), | 165 | may(linkType.call(p1, p2)), |
89 | not(may(targetType.call(p2))) | 166 | not(must(linkType.call(p1, p2))), |
167 | not(mayNewTarget.call(p2)) | ||
90 | ); | 168 | ); |
91 | if (info.isConstrained()) { | 169 | } |
92 | builder.clause( | 170 | builder.action( |
93 | may(linkType.call(p1, p2)), | 171 | merge(linkType, TruthValue.FALSE, p1, p2) |
94 | not(must(linkType.call(p1, p2))), | 172 | ); |
95 | not(mayNewSource.call(p1)) | 173 | })); |
96 | ); | ||
97 | builder.clause( | ||
98 | may(linkType.call(p1, p2)), | ||
99 | not(must(linkType.call(p1, p2))), | ||
100 | not(mayNewTarget.call(p2)) | ||
101 | ); | ||
102 | } | ||
103 | builder.action( | ||
104 | merge(linkType, TruthValue.FALSE, p1, p2) | ||
105 | ); | ||
106 | })); | ||
107 | } | ||
108 | translator.refiner(DirectedCrossReferenceRefiner.of(symbol, sourceType, targetType)); | ||
109 | translator.initializer(new DirectedCrossReferenceInitializer(linkType, sourceType, targetType, symbol)); | ||
110 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
111 | .clause( | ||
112 | may(linkType.call(source, target)), | ||
113 | not(candidateMust(linkType.call(source, target))), | ||
114 | not(MULTI_VIEW.call(source)), | ||
115 | not(MULTI_VIEW.call(target)) | ||
116 | ) | ||
117 | .action( | ||
118 | add(linkType, source, target) | ||
119 | ))); | ||
120 | storeBuilder.with(translator); | ||
121 | |||
122 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(sourceType, linkType, false, | ||
123 | info.sourceMultiplicity())); | ||
124 | |||
125 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(targetType, linkType, true, | ||
126 | info.targetMultiplicity())); | ||
127 | } | 174 | } |
128 | 175 | ||
129 | private RelationalQuery createMayHelper(PartialRelation type, Multiplicity multiplicity, boolean inverse) { | ||
130 | return CrossReferenceUtils.createMayHelper(linkType, type, multiplicity, inverse); | ||
131 | } | ||
132 | } | 176 | } |
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 index 560eb04a..78b9cd86 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -9,7 +9,8 @@ import tools.refinery.store.reasoning.representation.PartialRelation; | |||
9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | 9 | import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; |
10 | import tools.refinery.logic.term.truthvalue.TruthValue; | 10 | import tools.refinery.logic.term.truthvalue.TruthValue; |
11 | 11 | ||
12 | public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity, TruthValue defaultValue) { | 12 | public record UndirectedCrossReferenceInfo(PartialRelation type, Multiplicity multiplicity, TruthValue defaultValue, |
13 | boolean partial) { | ||
13 | public boolean isConstrained() { | 14 | public boolean isConstrained() { |
14 | return multiplicity.isConstrained(); | 15 | return multiplicity.isConstrained(); |
15 | } | 16 | } |
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 index 97c0b800..af0ddd2e 100644 --- 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 | |||
@@ -1,25 +1,26 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
6 | package tools.refinery.store.reasoning.translator.crossreference; | 6 | package tools.refinery.store.reasoning.translator.crossreference; |
7 | 7 | ||
8 | import tools.refinery.logic.dnf.Query; | ||
9 | import tools.refinery.logic.term.truthvalue.TruthValue; | ||
8 | import tools.refinery.store.dse.propagation.PropagationBuilder; | 10 | import tools.refinery.store.dse.propagation.PropagationBuilder; |
9 | import tools.refinery.store.dse.transition.Rule; | 11 | import tools.refinery.store.dse.transition.Rule; |
10 | import tools.refinery.store.model.ModelStoreBuilder; | 12 | import tools.refinery.store.model.ModelStoreBuilder; |
11 | import tools.refinery.store.model.ModelStoreConfiguration; | 13 | import tools.refinery.store.model.ModelStoreConfiguration; |
12 | import tools.refinery.logic.dnf.Query; | ||
13 | import tools.refinery.store.query.view.ForbiddenView; | 14 | import tools.refinery.store.query.view.ForbiddenView; |
14 | import tools.refinery.store.reasoning.lifting.DnfLifter; | 15 | import tools.refinery.store.reasoning.lifting.DnfLifter; |
15 | import tools.refinery.store.reasoning.literal.Concreteness; | 16 | import tools.refinery.store.reasoning.literal.Concreteness; |
16 | import tools.refinery.store.reasoning.literal.Modality; | 17 | import tools.refinery.store.reasoning.literal.Modality; |
17 | import tools.refinery.store.reasoning.representation.PartialRelation; | 18 | import tools.refinery.store.reasoning.representation.PartialRelation; |
18 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; | 19 | import tools.refinery.store.reasoning.translator.PartialRelationTranslator; |
20 | import tools.refinery.store.reasoning.translator.RoundingMode; | ||
19 | import tools.refinery.store.reasoning.translator.TranslationException; | 21 | import tools.refinery.store.reasoning.translator.TranslationException; |
20 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; | 22 | import tools.refinery.store.reasoning.translator.multiplicity.InvalidMultiplicityErrorTranslator; |
21 | import tools.refinery.store.representation.Symbol; | 23 | import tools.refinery.store.representation.Symbol; |
22 | import tools.refinery.logic.term.truthvalue.TruthValue; | ||
23 | 24 | ||
24 | import static tools.refinery.logic.literal.Literals.not; | 25 | import static tools.refinery.logic.literal.Literals.not; |
25 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; | 26 | import static tools.refinery.store.reasoning.actions.PartialActionLiterals.add; |
@@ -40,73 +41,106 @@ public class UndirectedCrossReferenceTranslator implements ModelStoreConfigurati | |||
40 | 41 | ||
41 | @Override | 42 | @Override |
42 | public void apply(ModelStoreBuilder storeBuilder) { | 43 | public void apply(ModelStoreBuilder storeBuilder) { |
43 | var name = linkType.name(); | ||
44 | var type = info.type(); | 44 | var type = info.type(); |
45 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
46 | |||
47 | var defaultValue = info.defaultValue(); | 45 | var defaultValue = info.defaultValue(); |
48 | if (defaultValue.must()) { | 46 | if (defaultValue.must()) { |
49 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" | 47 | throw new TranslationException(linkType, "Unsupported default value %s for directed cross references %s" |
50 | .formatted(defaultValue, linkType)); | 48 | .formatted(defaultValue, linkType)); |
51 | } | 49 | } |
52 | |||
53 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); | ||
54 | |||
55 | var translator = PartialRelationTranslator.of(linkType); | 50 | var translator = PartialRelationTranslator.of(linkType); |
56 | translator.symbol(symbol); | 51 | translator.symbol(symbol); |
57 | if (defaultValue.may()) { | 52 | if (defaultValue.may()) { |
58 | var forbiddenView = new ForbiddenView(symbol); | 53 | configureWithDefaultUnknown(translator); |
59 | translator.may(Query.of(mayName, (builder, source, target) -> { | 54 | } else { |
55 | configureWithDefaultFalse(storeBuilder); | ||
56 | } | ||
57 | translator.refiner(UndirectedCrossReferenceRefiner.of(symbol, type)); | ||
58 | translator.initializer(new UndirectedCrossReferenceInitializer(linkType, type, symbol)); | ||
59 | if (info.partial()) { | ||
60 | translator.roundingMode(RoundingMode.NONE); | ||
61 | } else { | ||
62 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder | ||
63 | .clause( | ||
64 | may(linkType.call(source, target)), | ||
65 | not(candidateMust(linkType.call(source, target))), | ||
66 | not(MULTI_VIEW.call(source)), | ||
67 | not(MULTI_VIEW.call(target)) | ||
68 | ) | ||
69 | .action( | ||
70 | add(linkType, source, target) | ||
71 | ))); | ||
72 | } | ||
73 | storeBuilder.with(translator); | ||
74 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); | ||
75 | } | ||
76 | |||
77 | private void configureWithDefaultUnknown(PartialRelationTranslator translator) { | ||
78 | var name = linkType.name(); | ||
79 | var type = info.type(); | ||
80 | var multiplicity = info.multiplicity(); | ||
81 | var mayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.PARTIAL); | ||
82 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, multiplicity, false); | ||
83 | var forbiddenView = new ForbiddenView(symbol); | ||
84 | translator.may(Query.of(mayName, (builder, source, target) -> { | ||
85 | builder.clause( | ||
86 | mayNewSource.call(source), | ||
87 | mayNewSource.call(target), | ||
88 | not(forbiddenView.call(source, target)) | ||
89 | ); | ||
90 | if (info.isConstrained()) { | ||
91 | // Violation of monotonicity: | ||
92 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
93 | // corresponding error pattern will already mark the node as invalid. | ||
94 | builder.clause( | ||
95 | must(linkType.call(source, target)), | ||
96 | not(forbiddenView.call(source, target)), | ||
97 | may(type.call(source)), | ||
98 | may(type.call(target)) | ||
99 | ); | ||
100 | } | ||
101 | })); | ||
102 | if (info.partial()) { | ||
103 | var candidateMayName = DnfLifter.decorateName(name, Modality.MAY, Concreteness.CANDIDATE); | ||
104 | var candidateMayNewSource = CrossReferenceUtils.createCandidateMayHelper(linkType, type, multiplicity, | ||
105 | false); | ||
106 | translator.candidateMay(Query.of(candidateMayName, (builder, source, target) -> { | ||
60 | builder.clause( | 107 | builder.clause( |
61 | mayNewSource.call(source), | 108 | candidateMayNewSource.call(source), |
62 | mayNewSource.call(target), | 109 | candidateMayNewSource.call(target), |
63 | not(forbiddenView.call(source, target)) | 110 | not(forbiddenView.call(source, target)) |
64 | ); | 111 | ); |
65 | if (info.isConstrained()) { | 112 | if (info.isConstrained()) { |
66 | // Violation of monotonicity: | ||
67 | // Edges violating upper multiplicity will not be marked as {@code ERROR}, but the | ||
68 | // corresponding error pattern will already mark the node as invalid. | ||
69 | builder.clause( | 113 | builder.clause( |
70 | must(linkType.call(source, target)), | 114 | candidateMust(linkType.call(source, target)), |
71 | not(forbiddenView.call(source, target)), | 115 | not(forbiddenView.call(source, target)), |
72 | may(type.call(source)), | 116 | candidateMay(type.call(source)), |
73 | may(type.call(target)) | 117 | candidateMay(type.call(target)) |
74 | ); | 118 | ); |
75 | } | 119 | } |
76 | })); | 120 | })); |
77 | } else { | 121 | } |
78 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | 122 | } |
79 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | 123 | |
124 | private void configureWithDefaultFalse(ModelStoreBuilder storeBuilder) { | ||
125 | var name = linkType.name(); | ||
126 | var type = info.type(); | ||
127 | var mayNewSource = CrossReferenceUtils.createMayHelper(linkType, type, info.multiplicity(), false); | ||
128 | var propagationBuilder = storeBuilder.getAdapter(PropagationBuilder.class); | ||
129 | propagationBuilder.rule(Rule.of(name + "#invalidLink", (builder, p1, p2) -> { | ||
130 | builder.clause( | ||
131 | may(linkType.call(p1, p2)), | ||
132 | not(may(type.call(p1))) | ||
133 | ); | ||
134 | if (info.isConstrained()) { | ||
80 | builder.clause( | 135 | builder.clause( |
81 | may(linkType.call(p1, p2)), | 136 | may(linkType.call(p1, p2)), |
82 | not(may(type.call(p1))) | 137 | not(must(linkType.call(p1, p2))), |
83 | ); | 138 | not(mayNewSource.call(p1)) |
84 | if (info.isConstrained()) { | ||
85 | builder.clause( | ||
86 | may(linkType.call(p1, p2)), | ||
87 | not(must(linkType.call(p1, p2))), | ||
88 | not(mayNewSource.call(p1)) | ||
89 | ); | ||
90 | } | ||
91 | builder.action( | ||
92 | merge(linkType, TruthValue.FALSE, p1, p2) | ||
93 | ); | 139 | ); |
94 | })); | 140 | } |
95 | } | 141 | builder.action( |
96 | translator.refiner(UndirectedCrossReferenceRefiner.of(symbol, type)); | 142 | merge(linkType, TruthValue.FALSE, p1, p2) |
97 | translator.initializer(new UndirectedCrossReferenceInitializer(linkType, type, symbol)); | 143 | ); |
98 | translator.decision(Rule.of(linkType.name(), (builder, source, target) -> builder | 144 | })); |
99 | .clause( | ||
100 | may(linkType.call(source, target)), | ||
101 | not(candidateMust(linkType.call(source, target))), | ||
102 | not(MULTI_VIEW.call(source)), | ||
103 | not(MULTI_VIEW.call(target)) | ||
104 | ) | ||
105 | .action( | ||
106 | add(linkType, source, target) | ||
107 | ))); | ||
108 | storeBuilder.with(translator); | ||
109 | |||
110 | storeBuilder.with(new InvalidMultiplicityErrorTranslator(type, linkType, false, info.multiplicity())); | ||
111 | } | 145 | } |
112 | } | 146 | } |
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 index d1979b8c..94bf1c5d 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -143,7 +143,7 @@ public class MetamodelBuilder { | |||
143 | targetType, linkType, sourceType)); | 143 | targetType, linkType, sourceType)); |
144 | } | 144 | } |
145 | undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, | 145 | undirectedCrossReferences.put(linkType, new UndirectedCrossReferenceInfo(sourceType, |
146 | info.multiplicity(), defaultValue)); | 146 | info.multiplicity(), defaultValue, info.partial())); |
147 | return; | 147 | return; |
148 | } | 148 | } |
149 | oppositeReferences.put(opposite, linkType); | 149 | oppositeReferences.put(opposite, linkType); |
@@ -153,7 +153,7 @@ public class MetamodelBuilder { | |||
153 | return; | 153 | return; |
154 | } | 154 | } |
155 | directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), | 155 | directedCrossReferences.put(linkType, new DirectedCrossReferenceInfo(sourceType, info.multiplicity(), |
156 | targetType, targetMultiplicity, defaultValue)); | 156 | targetType, targetMultiplicity, defaultValue, info.partial())); |
157 | } | 157 | } |
158 | 158 | ||
159 | private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info, | 159 | private void processContainmentInfo(PartialRelation linkType, ReferenceInfo info, |
@@ -197,5 +197,9 @@ public class MetamodelBuilder { | |||
197 | throw new TranslationException(opposite, "Opposite %s of containment %s cannot be containment" | 197 | throw new TranslationException(opposite, "Opposite %s of containment %s cannot be containment" |
198 | .formatted(opposite, linkType)); | 198 | .formatted(opposite, linkType)); |
199 | } | 199 | } |
200 | if (info.partial() != oppositeInfo.partial()) { | ||
201 | throw new TranslationException(opposite, "Either both %s and %s have to be partial or neither of them" | ||
202 | .formatted(opposite, linkType)); | ||
203 | } | ||
200 | } | 204 | } |
201 | } | 205 | } |
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 index 47a2e95f..94050dc0 100644 --- 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 | |||
@@ -10,7 +10,14 @@ import tools.refinery.store.reasoning.translator.multiplicity.Multiplicity; | |||
10 | import tools.refinery.logic.term.truthvalue.TruthValue; | 10 | import tools.refinery.logic.term.truthvalue.TruthValue; |
11 | 11 | ||
12 | public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, | 12 | public record ReferenceInfo(boolean containment, PartialRelation sourceType, Multiplicity multiplicity, |
13 | PartialRelation targetType, PartialRelation opposite, TruthValue defaultValue) { | 13 | PartialRelation targetType, PartialRelation opposite, TruthValue defaultValue, |
14 | boolean partial) { | ||
15 | public ReferenceInfo { | ||
16 | if (containment && partial) { | ||
17 | throw new IllegalArgumentException("Containment references cannot be partial"); | ||
18 | } | ||
19 | } | ||
20 | |||
14 | public static ReferenceInfoBuilder builder() { | 21 | public static ReferenceInfoBuilder builder() { |
15 | return new ReferenceInfoBuilder(); | 22 | return new ReferenceInfoBuilder(); |
16 | } | 23 | } |
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java index 39240d6b..90fb22b5 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/metamodel/ReferenceInfoBuilder.java | |||
@@ -21,6 +21,7 @@ public final class ReferenceInfoBuilder { | |||
21 | private PartialRelation targetType; | 21 | private PartialRelation targetType; |
22 | private PartialRelation opposite; | 22 | private PartialRelation opposite; |
23 | private TruthValue defaultValue = TruthValue.UNKNOWN; | 23 | private TruthValue defaultValue = TruthValue.UNKNOWN; |
24 | private boolean partial; | ||
24 | 25 | ||
25 | ReferenceInfoBuilder() { | 26 | ReferenceInfoBuilder() { |
26 | } | 27 | } |
@@ -72,6 +73,11 @@ public final class ReferenceInfoBuilder { | |||
72 | return this; | 73 | return this; |
73 | } | 74 | } |
74 | 75 | ||
76 | public ReferenceInfoBuilder partial(boolean partial) { | ||
77 | this.partial = partial; | ||
78 | return this; | ||
79 | } | ||
80 | |||
75 | public ReferenceInfo build() { | 81 | public ReferenceInfo build() { |
76 | if (sourceType == null) { | 82 | if (sourceType == null) { |
77 | throw new IllegalStateException("Source type is required"); | 83 | throw new IllegalStateException("Source type is required"); |
@@ -79,6 +85,6 @@ public final class ReferenceInfoBuilder { | |||
79 | if (targetType == null) { | 85 | if (targetType == null) { |
80 | throw new IllegalStateException("Target type is required"); | 86 | throw new IllegalStateException("Target type is required"); |
81 | } | 87 | } |
82 | return new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite, defaultValue); | 88 | return new ReferenceInfo(containment, sourceType, multiplicity, targetType, opposite, defaultValue, partial); |
83 | } | 89 | } |
84 | } | 90 | } |
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 index 0ca6eac2..e2eff921 100644 --- 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 | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> | 2 | * SPDX-FileCopyrightText: 20232-2024 The Refinery Authors <https://refinery.tools/> |
3 | * | 3 | * |
4 | * SPDX-License-Identifier: EPL-2.0 | 4 | * SPDX-License-Identifier: EPL-2.0 |
5 | */ | 5 | */ |
@@ -52,12 +52,11 @@ public class InvalidMultiplicityErrorTranslator implements ModelStoreConfigurati | |||
52 | 52 | ||
53 | @Override | 53 | @Override |
54 | public void apply(ModelStoreBuilder storeBuilder) { | 54 | public void apply(ModelStoreBuilder storeBuilder) { |
55 | if (!(multiplicity instanceof ConstrainedMultiplicity constrainedMultiplicity)) { | 55 | if (!(multiplicity instanceof ConstrainedMultiplicity(var cardinalityInterval, var errorSymbol))) { |
56 | return; | 56 | return; |
57 | } | 57 | } |
58 | 58 | ||
59 | var name = constrainedMultiplicity.errorSymbol().name(); | 59 | var name = errorSymbol.name(); |
60 | var cardinalityInterval = constrainedMultiplicity.multiplicity(); | ||
61 | var node = Variable.of("node"); | 60 | var node = Variable.of("node"); |
62 | var other = Variable.of("other"); | 61 | var other = Variable.of("other"); |
63 | List<Variable> arguments = inverse ? List.of(other, node) : List.of(node, other); | 62 | List<Variable> arguments = inverse ? List.of(other, node) : List.of(node, other); |
@@ -98,8 +97,7 @@ public class InvalidMultiplicityErrorTranslator implements ModelStoreConfigurati | |||
98 | )); | 97 | )); |
99 | } | 98 | } |
100 | 99 | ||
101 | if (cardinalityInterval.upperBound() instanceof FiniteUpperCardinality finiteUpperCardinality) { | 100 | if (cardinalityInterval.upperBound() instanceof FiniteUpperCardinality(int upperBound)) { |
102 | int upperBound = finiteUpperCardinality.finiteUpperBound(); | ||
103 | mustBuilder.clause(Integer.class, existingContents -> List.of( | 101 | mustBuilder.clause(Integer.class, existingContents -> List.of( |
104 | must(nodeType.call(node)), | 102 | must(nodeType.call(node)), |
105 | new CountLowerBoundLiteral(existingContents, linkType, arguments), | 103 | new CountLowerBoundLiteral(existingContents, linkType, arguments), |
@@ -128,7 +126,7 @@ public class InvalidMultiplicityErrorTranslator implements ModelStoreConfigurati | |||
128 | output.assign(missingBuilder.build().aggregate(INT_SUM, Variable.of())) | 126 | output.assign(missingBuilder.build().aggregate(INT_SUM, Variable.of())) |
129 | )); | 127 | )); |
130 | 128 | ||
131 | storeBuilder.with(PartialRelationTranslator.of(constrainedMultiplicity.errorSymbol()) | 129 | storeBuilder.with(PartialRelationTranslator.of(errorSymbol) |
132 | .mayNever() | 130 | .mayNever() |
133 | .must(mustBuilder.build()) | 131 | .must(mustBuilder.build()) |
134 | .candidateMay(candidateMayBuilder.build()) | 132 | .candidateMay(candidateMayBuilder.build()) |