diff options
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()) |